Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuca Bonavita <mindrones@gmail.com>2010-07-21 15:26:03 +0400
committerLuca Bonavita <mindrones@gmail.com>2010-07-21 15:26:03 +0400
commita4b4c07685dd2bd2aba8892b5f1fa90d30b0ed7a (patch)
tree7f2b1947086e11b2975b725d29c4ef10eb8e7c6a
== blender 2.53 release ==v2.53
- tagging trunk/ and contrib/ os we know what worked at 2.53 release time - only tags/2_53_release should be used for blender release of course [[Split portion of a mixed commit.]]
-rw-r--r--add_curve_aceous_galore.py1156
-rw-r--r--add_curve_torus_knots.py363
-rw-r--r--add_mesh_3d_function_surface.py648
-rw-r--r--add_mesh_extras.py791
-rw-r--r--add_mesh_gears.py949
-rw-r--r--add_mesh_gemstones.py480
-rw-r--r--add_mesh_pipe_joint.py1300
-rw-r--r--add_mesh_solid.py913
-rw-r--r--add_mesh_twisted_torus.py366
-rw-r--r--curve_simplify.py597
-rw-r--r--io_anim_camera.py159
-rw-r--r--io_export_directx_x.py1077
-rw-r--r--io_export_unreal_psk_psa.py1603
-rw-r--r--io_import_images_as_planes.py528
-rw-r--r--io_import_scene_mhx.py2246
-rw-r--r--io_import_scene_unreal_psk.py595
-rw-r--r--io_mesh_raw/__init__.py63
-rw-r--r--io_mesh_raw/export_raw.py112
-rw-r--r--io_mesh_raw/import_raw.py139
-rw-r--r--io_mesh_stl/__init__.py152
-rw-r--r--io_mesh_stl/blender_utils.py50
-rw-r--r--io_mesh_stl/stl_utils.py228
-rw-r--r--mesh_relax.py128
-rw-r--r--mesh_surface_sketch.py827
-rw-r--r--object_add_chain.py156
-rw-r--r--object_cloud_gen.py679
-rw-r--r--object_fracture/__init__.py86
-rw-r--r--object_fracture/data.blendbin0 -> 232271 bytes
-rw-r--r--object_fracture/fracture_ops.py492
-rw-r--r--object_fracture/fracture_setup.py74
-rw-r--r--render_renderfarmfi.py799
-rw-r--r--space_view3d_align_tools.py361
-rw-r--r--space_view3d_materials_utils.py704
-rw-r--r--space_view3d_panel_measure.py989
-rw-r--r--space_view3d_property_chart.py232
-rw-r--r--space_view3d_spacebar_menu.py1501
36 files changed, 21543 insertions, 0 deletions
diff --git a/add_curve_aceous_galore.py b/add_curve_aceous_galore.py
new file mode 100644
index 00000000..0b546d5d
--- /dev/null
+++ b/add_curve_aceous_galore.py
@@ -0,0 +1,1156 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+bl_addon_info = {
+ 'name': 'Add Curve: Curveaceous Galore!',
+ 'author': 'Jimmy Hazevoet, testscreenings',
+ 'version': '0.1',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Curve',
+ 'description': 'Adds many types of curves',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Curve/Curves_Galore',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22404&group_id=153&atid=469',
+ 'category': 'Add Curve'}
+
+
+##------------------------------------------------------------
+#### import modules
+import bpy
+from bpy.props import *
+from mathutils import *
+from math import *
+###------------------------------------------------------------
+#### Some functions to use with others:
+###------------------------------------------------------------
+'''
+#------------------------------------------------------------
+# Generate random number:
+def randnum( low=0.0, high=1.0, seed=0 ):
+ """
+ randnum( low=0.0, high=1.0, seed=0 )
+
+ Create random number
+
+ Parameters:
+ low - lower range
+ (type=float)
+ high - higher range
+ (type=float)
+ seed - the random seed number, if seed is 0, the current time will be used instead
+ (type=int)
+ Returns:
+ a random number
+ (type=float)
+ """
+
+ s = Noise.setRandomSeed( seed )
+ rnum = Noise.random()
+ rnum = rnum*(high-low)
+ rnum = rnum+low
+ return rnum
+
+
+
+#------------------------------------------------------------
+# Make some noise:
+def vTurbNoise((x,y,z), iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0 ):
+ """
+ vTurbNoise((x,y,z), iScale=0.25, Size=1.0, Depth=6, Hard=0, Basis=0, Seed=0 )
+
+ Create randomised vTurbulence noise
+
+ Parameters:
+ xyz - (x,y,z) float values.
+ (type=3-float tuple)
+ iScale - noise intensity scale
+ (type=float)
+ Size - noise size
+ (type=float)
+ Depth - number of noise values added.
+ (type=int)
+ Hard - noise hardness: 0 - soft noise; 1 - hard noise
+ (type=int)
+ basis - type of noise used for turbulence
+ (type=int)
+ Seed - the random seed number, if seed is 0, the current time will be used instead
+ (type=int)
+ Returns:
+ the generated turbulence vector.
+ (type=3-float list)
+ """
+
+ rand = randnum(-100,100,Seed)
+ if Basis ==9: Basis = 14
+ vTurb = Noise.vTurbulence(( x/Size+rand, y/Size+rand, z/Size+rand ), Depth, Hard, Basis )
+ tx = vTurb[0]*iScale
+ ty = vTurb[1]*iScale
+ tz = vTurb[2]*iScale
+ return tx,ty,tz
+
+
+
+#------------------------------------------------------------
+# Axis: ( used in 3DCurve Turbulence )
+def AxisFlip((x,y,z), x_axis=1, y_axis=1, z_axis=1, flip=0 ):
+ if flip != 0:
+ flip *= -1
+ else: flip = 1
+ x *= x_axis*flip
+ y *= y_axis*flip
+ z *= z_axis*flip
+ return x,y,z
+'''
+
+###-------------------------------------------------------------------
+#### 2D Curve shape functions:
+###-------------------------------------------------------------------
+
+##------------------------------------------------------------
+# 2DCurve: Profile: L, H, T, U, Z
+def ProfileCurve(type=0, a=0.25, b=0.25):
+ """
+ ProfileCurve( type=0, a=0.25, b=0.25 )
+
+ Create profile curve
+
+ Parameters:
+ type - select profile type, L, H, T, U, Z
+ (type=int)
+ a - a scaling parameter
+ (type=float)
+ b - b scaling parameter
+ (type=float)
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ if type ==1:
+ ## H:
+ a*=0.5
+ b*=0.5
+ newpoints = [ [ -1.0, 1.0, 0.0 ], [ -1.0+a, 1.0, 0.0 ],
+ [ -1.0+a, b, 0.0 ], [ 1.0-a, b, 0.0 ], [ 1.0-a, 1.0, 0.0 ],
+ [ 1.0, 1.0, 0.0 ], [ 1.0, -1.0, 0.0 ], [ 1.0-a, -1.0, 0.0 ],
+ [ 1.0-a, -b, 0.0 ], [ -1.0+a, -b, 0.0 ], [ -1.0+a, -1.0, 0.0 ],
+ [ -1.0, -1.0, 0.0 ] ]
+ elif type ==2:
+ ## T:
+ a*=0.5
+ newpoints = [ [ -1.0, 1.0, 0.0 ], [ 1.0, 1.0, 0.0 ],
+ [ 1.0, 1.0-b, 0.0 ], [ a, 1.0-b, 0.0 ], [ a, -1.0, 0.0 ],
+ [ -a, -1.0, 0.0 ], [ -a, 1.0-b, 0.0 ], [ -1.0, 1.0-b, 0.0 ] ]
+ elif type ==3:
+ ## U:
+ a*=0.5
+ newpoints = [ [ -1.0, 1.0, 0.0 ], [ -1.0+a, 1.0, 0.0 ],
+ [ -1.0+a, -1.0+b, 0.0 ], [ 1.0-a, -1.0+b, 0.0 ], [ 1.0-a, 1.0, 0.0 ],
+ [ 1.0, 1.0, 0.0 ], [ 1.0, -1.0, 0.0 ], [ -1.0, -1.0, 0.0 ] ]
+ elif type ==4:
+ ## Z:
+ a*=0.5
+ newpoints = [ [ -0.5, 1.0, 0.0 ], [ a, 1.0, 0.0 ],
+ [ a, -1.0+b, 0.0 ], [ 1.0, -1.0+b, 0.0 ], [ 1.0, -1.0, 0.0 ],
+ [ -a, -1.0, 0.0 ], [ -a, 1.0-b, 0.0 ], [ -1.0, 1.0-b, 0.0 ],
+ [ -1.0, 1.0, 0.0 ] ]
+ else:
+ ## L:
+ newpoints = [ [ -1.0, 1.0, 0.0 ], [ -1.0+a, 1.0, 0.0 ],
+ [ -1.0+a, -1.0+b, 0.0 ], [ 1.0, -1.0+b, 0.0 ],
+ [ 1.0, -1.0, 0.0 ], [ -1.0, -1.0, 0.0 ] ]
+ return newpoints
+
+##------------------------------------------------------------
+# 2DCurve: Miscellaneous.: Diamond, Arrow1, Arrow2, Square, ....
+def MiscCurve(type=1, a=1.0, b=0.5, c=90.0):
+ """
+ MiscCurve( type=1, a=1.0, b=0.5, c=90.0 )
+
+ Create miscellaneous curves
+
+ Parameters:
+ type - select type, Diamond, Arrow1, Arrow2, Square
+ (type=int)
+ a - a scaling parameter
+ (type=float)
+ b - b scaling parameter
+ (type=float)
+ c - c scaling parameter
+ (type=float)
+ doesn't seem to do anything
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ a*=0.5
+ b*=0.5
+ if type ==1:
+ ## diamond:
+ newpoints = [ [ 0.0, b, 0.0 ], [ a, 0.0, 0.0 ], [ 0.0, -b, 0.0 ], [ -a, 0.0, 0.0 ] ]
+ elif type ==2:
+ ## Arrow1:
+ newpoints = [ [ -a, b, 0.0 ], [ a, 0.0, 0.0 ], [ -a, -b, 0.0 ], [ 0.0, 0.0, 0.0 ] ]
+ elif type ==3:
+ ## Arrow2:
+ newpoints = [ [ -1.0, b, 0.0 ], [ -1.0+a, b, 0.0 ],
+ [ -1.0+a, 1.0, 0.0 ], [ 1.0, 0.0, 0.0 ],
+ [ -1.0+a, -1.0, 0.0 ], [ -1.0+a, -b, 0.0 ],
+ [ -1.0, -b, 0.0 ] ]
+ elif type ==4:
+ ## Rounded square:
+ newpoints = [ [ -a, b-b*0.2, 0.0 ], [ -a+a*0.05, b-b*0.05, 0.0 ], [ -a+a*0.2, b, 0.0 ],
+ [ a-a*0.2, b, 0.0 ], [ a-a*0.05, b-b*0.05, 0.0 ], [ a, b-b*0.2, 0.0 ],
+ [ a, -b+b*0.2, 0.0 ], [ a-a*0.05, -b+b*0.05, 0.0 ], [ a-a*0.2, -b, 0.0 ],
+ [ -a+a*0.2, -b, 0.0 ], [ -a+a*0.05, -b+b*0.05, 0.0 ], [ -a, -b+b*0.2, 0.0 ] ]
+
+ #elif type ==15:
+ ## :
+ #newpoints = [ [ x,y,z ] ]
+ else:
+ ## Square:
+ newpoints = [ [ -a, b, 0.0 ], [ a, b, 0.0 ], [ a, -b, 0.0 ], [ -a, -b, 0.0 ] ]
+ return newpoints
+
+##------------------------------------------------------------
+# 2DCurve: Star:
+def StarCurve(starpoints=8, innerradius=0.5, outerradius=1.0, twist=0.0):
+ """
+ StarCurve( starpoints=8, innerradius=0.5, outerradius=1.0, twist=0.0 )
+
+ Create star shaped curve
+
+ Parameters:
+ starpoints - the number of points
+ (type=int)
+ innerradius - innerradius
+ (type=float)
+ outerradius - outerradius
+ (type=float)
+ twist - twist amount
+ (type=float)
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ step = (2.0/(starpoints))
+ i = 0
+ while i < starpoints:
+ t = (i*step)
+ x1 = cos(t*pi)*outerradius
+ y1 = sin(t*pi)*outerradius
+ newpoints.append([x1,y1,0])
+ x2 = cos(t*pi+(pi/starpoints+twist))*innerradius
+ y2 = sin(t*pi+(pi/starpoints+twist))*innerradius
+ newpoints.append([x2,y2,0])
+ i+=1
+ return newpoints
+
+##------------------------------------------------------------
+# 2DCurve: Flower:
+def FlowerCurve(petals=8, innerradius=0.5, outerradius=1.0, petalwidth=2.0):
+ """
+ FlowerCurve( petals=8, innerradius=0.5, outerradius=1.0, petalwidth=2.0 )
+
+ Create flower shaped curve
+
+ Parameters:
+ petals - the number of petals
+ (type=int)
+ innerradius - innerradius
+ (type=float)
+ outerradius - outerradius
+ (type=float)
+ petalwidth - width of petals
+ (type=float)
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ step = (2.0/(petals))
+ pet = (step/pi*2)*petalwidth
+ i = 0
+ while i < petals:
+ t = (i*step)
+ x1 = cos(t*pi-(pi/petals))*innerradius
+ y1 = sin(t*pi-(pi/petals))*innerradius
+ newpoints.append([x1,y1,0])
+ x2 = cos(t*pi-pet)*outerradius
+ y2 = sin(t*pi-pet)*outerradius
+ newpoints.append([x2,y2,0])
+ x3 = cos(t*pi+pet)*outerradius
+ y3 = sin(t*pi+pet)*outerradius
+ newpoints.append([x3,y3,0])
+ i+=1
+ return newpoints
+
+##------------------------------------------------------------
+# 2DCurve: Arc,Sector,Segment,Ring:
+def ArcCurve(sides=6, startangle=0.0, endangle=90.0, innerradius=0.5, outerradius=1.0, type=3):
+ """
+ ArcCurve( sides=6, startangle=0.0, endangle=90.0, innerradius=0.5, outerradius=1.0, type=3 )
+
+ Create arc shaped curve
+
+ Parameters:
+ sides - number of sides
+ (type=int)
+ startangle - startangle
+ (type=float)
+ endangle - endangle
+ (type=float)
+ innerradius - innerradius
+ (type=float)
+ outerradius - outerradius
+ (type=float)
+ type - select type Arc,Sector,Segment,Ring
+ (type=int)
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ sides += 1
+ angle = (2.0*(1.0/360.0))
+ endangle-=startangle
+ step = ((angle*endangle)/(sides-1))
+ i = 0
+ while i < sides:
+ t = (i*step) + angle*startangle
+ x1 = sin(t*pi)*outerradius
+ y1 = cos(t*pi)*outerradius
+ newpoints.append([x1,y1,0])
+ i+=1
+
+ #if type ==0:
+ # Arc: turn cyclic curve flag off!
+
+ # Segment:
+ if type ==2:
+ newpoints.append([0,0,0])
+ # Ring:
+ elif type ==3:
+ j=sides-1
+ while j > -1:
+ t = (j*step) + angle*startangle
+ x2 = sin(t*pi)*innerradius
+ y2 = cos(t*pi)*innerradius
+ newpoints.append([x2,y2,0])
+ j-=1
+ return newpoints
+
+##------------------------------------------------------------
+# 2DCurve: Cog wheel:
+def CogCurve(theeth=8, innerradius=0.8, middleradius=0.95, outerradius=1.0, bevel=0.5):
+ """
+ CogCurve( theeth=8, innerradius=0.8, middleradius=0.95, outerradius=1.0, bevel=0.5 )
+
+ Create cog wheel shaped curve
+
+ Parameters:
+ theeth - number of theeth
+ (type=int)
+ innerradius - innerradius
+ (type=float)
+ middleradius - middleradius
+ (type=float)
+ outerradius - outerradius
+ (type=float)
+ bevel - bevel amount
+ (type=float)
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ step = (2.0/(theeth))
+ pet = (step/pi*2)
+ bevel = 1.0-bevel
+ i = 0
+ while i < theeth:
+ t = (i*step)
+ x1 = cos(t*pi-(pi/theeth)-pet)*innerradius
+ y1 = sin(t*pi-(pi/theeth)-pet)*innerradius
+ newpoints.append([x1,y1,0])
+ x2 = cos(t*pi-(pi/theeth)+pet)*innerradius
+ y2 = sin(t*pi-(pi/theeth)+pet)*innerradius
+ newpoints.append([x2,y2,0])
+ x3 = cos(t*pi-pet)*middleradius
+ y3 = sin(t*pi-pet)*middleradius
+ newpoints.append([x3,y3,0])
+ x4 = cos(t*pi-(pet*bevel))*outerradius
+ y4 = sin(t*pi-(pet*bevel))*outerradius
+ newpoints.append([x4,y4,0])
+ x5 = cos(t*pi+(pet*bevel))*outerradius
+ y5 = sin(t*pi+(pet*bevel))*outerradius
+ newpoints.append([x5,y5,0])
+ x6 = cos(t*pi+pet)*middleradius
+ y6 = sin(t*pi+pet)*middleradius
+ newpoints.append([x6,y6,0])
+ i+=1
+ return newpoints
+
+##------------------------------------------------------------
+# 2DCurve: nSide:
+def nSideCurve(sides=6, radius=1.0):
+ """
+ nSideCurve( sides=6, radius=1.0 )
+
+ Create n-sided curve
+
+ Parameters:
+ sides - number of sides
+ (type=int)
+ radius - radius
+ (type=float)
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ step = (2.0/(sides))
+ i = 0
+ while i < sides:
+ t = (i*step)
+ x = sin(t*pi)*radius
+ y = cos(t*pi)*radius
+ newpoints.append([x,y,0])
+ i+=1
+ return newpoints
+
+'''
+##------------------------------------------------------------
+# 2DCurve: Splat:
+def SplatCurve(sides=24, scale=1.0, seed=0, basis=0, radius=1.0):
+ """
+ SplatCurve( sides=24, scale=1.0, seed=0, basis=0, radius=1.0 )
+
+ Create splat curve
+
+ Parameters:
+ sides - number of sides
+ (type=int)
+ scale - noise size
+ (type=float)
+ seed - noise random seed
+ (type=int)
+ basis - noise basis
+ (type=int)
+ radius - radius
+ (type=float)
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ step = (2.0/(sides))
+ i = 0
+ while i < sides:
+ t = (i*step)
+ turb = vTurbNoise(t, 1.0, scale, 6, 0, basis, seed )
+ turb = turb[2] * 0.5 + 0.5
+ x = sin(t*pi)*radius * turb
+ y = cos(t*pi)*radius * turb
+ newpoints.append([x,y,0])
+ i+=1
+ return newpoints
+'''
+###-----------------------------------------------------------
+#### 3D curve shape functions:
+###-----------------------------------------------------------
+
+###------------------------------------------------------------
+# 3DCurve: Helix:
+def HelixCurve( number=100, height=2.0, startangle=0.0, endangle=360.0, width=1.0, a=0.0, b=0.0 ):
+ """
+ HelixCurve( number=100, height=2.0, startangle=0.0, endangle=360.0, width=1.0, a=0.0, b=0.0 )
+
+ Create helix curve
+
+ Parameters:
+ number - the number of points
+ (type=int)
+ height - height
+ (type=float)
+ startangle - startangle
+ (type=float)
+ endangle - endangle
+ (type=float)
+ width - width
+ (type=float)
+ a - a
+ (type=float)
+ b - b
+ (type=float)
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ angle = (2.0/360.0)*(endangle-startangle)
+ step = angle/(number-1)
+ h = height/angle
+ start = (startangle*2.0/360.0)
+ a/=angle
+ i = 0
+ while i < number:
+ t = ( i*step+start )
+ x = sin( (t*pi) ) * ( 1.0 + cos( t * pi * a - ( b * pi ) ) ) * ( 0.25 * width )
+ y = cos( (t*pi) ) * ( 1.0 + cos( t * pi * a - ( b * pi ) ) ) * ( 0.25 * width )
+ z = ( t * h ) -h*start
+ newpoints.append([x,y,z])
+ i+=1
+ return newpoints
+
+###------------------------------------------------------------ ?
+# 3DCurve: Cycloid: Cycloid, Epicycloid, Hypocycloid
+def CycloidCurve( number=24, length=2.0, type=0, a=1.0, b=1.0, startangle=0.0, endangle=360.0 ):
+ """
+ CycloidCurve( number=24, length=2.0, type=0, a=1.0, b=1.0, startangle=0.0, endangle=360.0 )
+
+ Create a Cycloid, Epicycloid or Hypocycloid curve
+
+ Parameters:
+ number - the number of points
+ (type=int)
+ length - length of curve
+ (type=float)
+ type - types: Cycloid, Epicycloid, Hypocycloid
+ (type=int)
+ Returns:
+ a list with lists of x,y,z coordinates for curve points, [[x,y,z],[x,y,z],...n]
+ (type=list)
+ """
+
+ newpoints = []
+ angle = (2.0/360.0)*(endangle-startangle)
+ step = angle/(number-1)
+ #h = height/angle
+ d = length
+ start = (startangle*2.0/360.0)
+ a/=angle
+ i = 0
+ if type == 0: # Epitrochoid
+ while i < number:
+ t = ( i*step+start )
+ x = ((a + b) * cos(t*pi)) - (d * cos(((a+b)/b)*t*pi))
+ y = ((a + b) * sin(t*pi)) - (d * sin(((a+b)/b)*t*pi))
+ z = 0 # ( t * h ) -h*start
+ newpoints.append([x,y,z])
+ i+=1
+
+ else:
+ newpoints = [[-1,-1,0], [-1,1,0], [1,1,0], [1,-1,0]]
+ return newpoints
+
+##------------------------------------------------------------
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = TranslationMatrix(context.scene.cursor_location)
+ obj_align = context.user_preferences.edit.object_align
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ rot = context.space_data.region_3d.view_matrix.rotation_part().invert().resize4x4()
+ else:
+ rot = Matrix()
+ align_matrix = loc * rot
+ return align_matrix
+
+##------------------------------------------------------------
+#### Curve creation functions
+# sets bezierhandles to auto
+def setBezierHandles(obj, mode = 'AUTOMATIC'):
+ scene = bpy.context.scene
+ if obj.type != 'CURVE':
+ return
+ scene.objects.active = obj
+ bpy.ops.object.mode_set(mode='EDIT', toggle=True)
+ bpy.ops.curve.select_all(action='SELECT')
+ bpy.ops.curve.handle_type_set(type=mode)
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=True)
+
+# get array of vertcoordinates acording to splinetype
+def vertsToPoints(Verts, splineType):
+ # main vars
+ vertArray = []
+
+ # array for BEZIER spline output (V3)
+ if splineType == 'BEZIER':
+ for v in Verts:
+ vertArray += v
+
+ # array for nonBEZIER output (V4)
+ else:
+ for v in Verts:
+ vertArray += v
+ if splineType == 'NURBS':
+ vertArray.append(1) #for nurbs w=1
+ else: #for poly w=0
+ vertArray.append(0)
+ return vertArray
+
+# create new CurveObject from vertarray and splineType
+def createCurve(vertArray, options, curveOptions, align_matrix):
+ # options to vars
+ splineType = options[0] # output splineType 'POLY' 'NURBS' 'BEZIER'
+ name = options[1] # GalloreType as name
+
+ # create curve
+ scene = bpy.context.scene
+ newCurve = bpy.data.curves.new(name, type = 'CURVE') # curvedatablock
+ newSpline = newCurve.splines.new(type = splineType) # spline
+
+ # create spline from vertarray
+ if splineType == 'BEZIER':
+ newSpline.bezier_points.add(int(len(vertArray)*0.33))
+ newSpline.bezier_points.foreach_set('co', vertArray)
+ else:
+ newSpline.points.add(int(len(vertArray)*0.25 - 1))
+ newSpline.points.foreach_set('co', vertArray)
+ newSpline.endpoint_u = True
+
+ # set curveOptions
+ shape = curveOptions[0]
+ cyclic_u = curveOptions[1]
+ endp_u = curveOptions[2]
+ order_u = curveOptions[3]
+ handleType = curveOptions[4]
+
+ newCurve.dimensions = shape
+ newSpline.cyclic_u = cyclic_u
+ newSpline.endpoint_u = endp_u
+ newSpline.order_u = order_u
+
+ # create object with newCurve
+ new_obj = bpy.data.objects.new(name, newCurve) # object
+ scene.objects.link(new_obj) # place in active scene
+ new_obj.select = True # set as selected
+ scene.objects.active = new_obj # set as active
+ new_obj.matrix_world = align_matrix # apply matrix
+
+ # set bezierhandles
+ if splineType == 'BEZIER':
+ setBezierHandles(new_obj, handleType)
+
+ return
+
+##------------------------------------------------------------
+# Main Function
+def main(context, options, curveOptions, align_matrix):
+ # deselect all objects
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # options
+ galType = options[1]
+ splineType = options[0]
+ innerRadius = options[9]
+ middleRadius = options[10]
+ outerRadius = options[11]
+
+ # get verts
+ if galType == 'Profile':
+ verts = ProfileCurve(options[2], options[3], options[4])
+ if galType == 'Miscellaneous':
+ verts = MiscCurve(options[5], options[6], options[7], options[8])
+ if galType == 'Flower':
+ verts = FlowerCurve(options[12], innerRadius, outerRadius, options[13])
+ if galType == 'Star':
+ verts = StarCurve(options[14], innerRadius, outerRadius, options[15])
+ if galType == 'Arc':
+ verts = ArcCurve(options[16], options[17], options[18], innerRadius, outerRadius, options[19])
+ if galType == 'Cogwheel':
+ verts = CogCurve(options[20], innerRadius, middleRadius, outerRadius, options[21])
+ if galType == 'Nsided':
+ verts = nSideCurve(options[22], outerRadius)
+# if galType == 'Splat':
+# verts = SplatCurve(options[23], options[24], options[25], options[26], outerRadius)
+ if galType == 'Helix':
+ verts = HelixCurve(options[27], options[28], options[29], options[30], options[31], options[32], options[33])
+ if galType == 'Cycloid':
+ verts = CycloidCurve(options[34], options[35], options[36], options[37], options[38], options[39], options[40])
+
+ # turn verts into array
+ vertArray = vertsToPoints(verts, splineType)
+
+ # create object
+ createCurve(vertArray, options, curveOptions, align_matrix)
+
+ return
+
+class Curveaceous_galore(bpy.types.Operator):
+ ''''''
+ bl_idname = "curveaceous_galore"
+ bl_label = "Curveaceous galore"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = "adds many types of curves"
+
+ # align_matrix for the invoke
+ align_matrix = Matrix()
+
+ #### general properties
+ GalloreTypes = [
+ ('Profile', 'Profile', 'Profile'),
+ ('Miscellaneous', 'Miscellaneous', 'Miscellaneous'),
+ ('Flower', 'Flower', 'Flower'),
+ ('Star', 'Star', 'Star'),
+ ('Arc', 'Arc', 'Arc'),
+ ('Cogwheel', 'Cogwheel', 'Cogwheel'),
+ ('Nsided', 'Nsided', 'Nsided'),
+# ('Splat', 'Splat', 'Splat'),
+ ('Cycloid', 'Cycloid', 'Cycloid'),
+ ('Helix', 'Helix (3D)', 'Helix')]
+ GalloreType = EnumProperty(name="Type",
+ description="Form of Curve to create",
+ items=GalloreTypes)
+ SplineTypes = [
+ ('POLY', 'Poly', 'POLY'),
+ ('NURBS', 'Nurbs', 'NURBS'),
+ ('BEZIER', 'Bezier', 'BEZIER')]
+ outputType = EnumProperty(name="Output splines",
+ description="Type of splines to output",
+ items=SplineTypes)
+
+ #### Curve Options
+ shapeItems = [
+ ('2D', '2D', '2D'),
+ ('3D', '3D', '3D')]
+ shape = EnumProperty(name="2D / 3D",
+ items=shapeItems,
+ description="2D or 3D Curve")
+ cyclic_u = BoolProperty(name="Cyclic",
+ default=True,
+ description="make curve closed")
+ endp_u = BoolProperty(name="endpoint_u",
+ default=True,
+ description="stretch to endpoints")
+ order_u = IntProperty(name="order_u",
+ default=4,
+ min=2, soft_min=2,
+ max=6, soft_max=6,
+ description="Order of nurbs spline")
+ bezHandles = [
+ ('VECTOR', 'Vector', 'VECTOR'),
+ ('AUTOMATIC', 'Auto', 'AUTOMATIC')]
+ handleType = EnumProperty(name="Handle type",
+ description="bezier handles type",
+ items=bezHandles)
+
+ #### ProfileCurve properties
+ ProfileCurveType = IntProperty(name="Type",
+ min=1, soft_min=1,
+ max=5, soft_max=5,
+ default=1,
+ description="Type of ProfileCurve")
+ ProfileCurvevar1 = FloatProperty(name="var_1",
+ default=0.25,
+ description="var1 of ProfileCurve")
+ ProfileCurvevar2 = FloatProperty(name="var_2",
+ default=0.25,
+ description="var2 of ProfileCurve")
+
+ #### MiscCurve properties
+ MiscCurveType = IntProperty(name="Type",
+ min=1, soft_min=1,
+ max=5, soft_max=5,
+ default=1,
+ description="Type of ProfileCurve")
+ MiscCurvevar1 = FloatProperty(name="var_1",
+ default=1.0,
+ description="var1 of ProfileCurve")
+ MiscCurvevar2 = FloatProperty(name="var_2",
+ default=0.5,
+ description="var2 of ProfileCurve")
+ MiscCurvevar3 = FloatProperty(name="var_3", # doesn't seem to do anything
+ default=90.0,
+ description="var3 of ProfileCurve")
+
+ #### Common properties
+ innerRadius = FloatProperty(name="Inner radius",
+ default=0.5,
+ min=0, soft_min=0,
+ description="Inner radius")
+ middleRadius = FloatProperty(name="Middle radius",
+ default=0.95,
+ min=0, soft_min=0,
+ description="Middle radius")
+ outerRadius = FloatProperty(name="Outer radius",
+ default=1.0,
+ min=0, soft_min=0,
+ description="Outer radius")
+
+ #### Flower properties
+ petals = IntProperty(name="Petals",
+ default=8,
+ min=2, soft_min=2,
+ description="Number of petals")
+ petalWidth = FloatProperty(name="Petal width",
+ default=2.0,
+ min=0.01, soft_min=0.01,
+ description="Petal width")
+
+ #### Star properties
+ starPoints = IntProperty(name="Star points",
+ default=8,
+ min=2, soft_min=2,
+ description="Number of star points")
+ starTwist = FloatProperty(name="Twist",
+ default=0.0,
+ description="Twist")
+
+ #### Arc properties
+ arcSides = IntProperty(name="Arc sides",
+ default=6,
+ min=1, soft_min=1,
+ description="Sides of arc")
+ startAngle = FloatProperty(name="Start angle",
+ default=0.0,
+ description="Start angle")
+ endAngle = FloatProperty(name="End angle",
+ default=90.0,
+ description="End angle")
+ arcType = IntProperty(name="Arc type",
+ default=3,
+ min=1, soft_min=1,
+ max=3, soft_max=3,
+ description="Sides of arc")
+
+ #### Cogwheel properties
+ teeth = IntProperty(name="Teeth",
+ default=8,
+ min=2, soft_min=2,
+ description="number of teeth")
+ bevel = FloatProperty(name="Bevel",
+ default=0.5,
+ min=0, soft_min=0,
+ max=1, soft_max=1,
+ description="Bevel")
+
+ #### Nsided property
+ Nsides = IntProperty(name="Sides",
+ default=8,
+ min=3, soft_min=3,
+ description="Number of sides")
+
+ #### Splat properties
+ splatSides = IntProperty(name="Splat sides",
+ default=24,
+ min=3, soft_min=3,
+ description="Splat sides")
+ splatScale = FloatProperty(name="Splat scale",
+ default=1.0,
+ min=0, soft_min=0,
+ description="Splat scale")
+ seed = IntProperty(name="Seed",
+ default=0,
+ min=0, soft_min=0,
+ description="Seed")
+ basis = IntProperty(name="Basis",
+ default=0,
+ min=0, soft_min=0,
+ description="Basis")
+
+ #### Helix properties
+ helixPoints = IntProperty(name="resolution",
+ default=100,
+ min=3, soft_min=3,
+ description="resolution")
+ helixHeight = FloatProperty(name="Height",
+ default=2.0,
+ min=0, soft_min=0,
+ description="Helix height")
+ helixStart = FloatProperty(name="Start angle",
+ default=0.0,
+ description="Helix start angle")
+ helixEnd = FloatProperty(name="Endangle",
+ default=360.0,
+ description="Helix end angle")
+ helixWidth = FloatProperty(name="Width",
+ default=1.0,
+ description="Helix width")
+ helix_a = FloatProperty(name="var_1",
+ default=0.0,
+ description="Helix var1")
+ helix_b = FloatProperty(name="var_2",
+ default=0.0,
+ description="Helix var2")
+
+ #### Cycloid properties
+ cycloPoints = IntProperty(name="Resolution",
+ default=100,
+ min=3, soft_min=3,
+ description="Resolution")
+ cyclo_d = FloatProperty(name="var_3",
+ default=1.5,
+ description="Cycloid var3")
+ cycloType = IntProperty(name="Type",
+ default=0,
+ min=0, soft_min=0,
+ max=0, soft_max=0,
+ description="resolution")
+ cyclo_a = FloatProperty(name="var_1",
+ default=5.0,
+ min=0.01, soft_min=0.01,
+ description="Cycloid var1")
+ cyclo_b = FloatProperty(name="var_2",
+ default=0.5,
+ min=0.01, soft_min=0.01,
+ description="Cycloid var2")
+ cycloStart = FloatProperty(name="Start angle",
+ default=0.0,
+ description="Cycloid start angle")
+ cycloEnd = FloatProperty(name="End angle",
+ default=360.0,
+ description="Cycloid end angle")
+
+ ##### DRAW #####
+ def draw(self, context):
+ props = self.properties
+ layout = self.layout
+
+ # general options
+ col = layout.column()
+ col.prop(props, 'GalloreType')
+ col.label(text=props.GalloreType+" Options")
+
+ # options per GalloreType
+ box = layout.box()
+ if props.GalloreType == 'Profile':
+ box.prop(props, 'ProfileCurveType')
+ box.prop(props, 'ProfileCurvevar1')
+ box.prop(props, 'ProfileCurvevar2')
+ if props.GalloreType == 'Miscellaneous':
+ box.prop(props, 'MiscCurveType')
+ box.prop(props, 'MiscCurvevar1')
+ box.prop(props, 'MiscCurvevar2')
+ #box.prop(props, 'MiscCurvevar3') # doesn't seem to do anything
+ if props.GalloreType == 'Flower':
+ box.prop(props, 'petals')
+ box.prop(props, 'petalWidth')
+ box.prop(props, 'innerRadius')
+ box.prop(props, 'outerRadius')
+ if props.GalloreType == 'Star':
+ box.prop(props, 'starPoints')
+ box.prop(props, 'starTwist')
+ box.prop(props, 'innerRadius')
+ box.prop(props, 'outerRadius')
+ if props.GalloreType == 'Arc':
+ box.prop(props, 'arcSides')
+ box.prop(props, 'arcType') # has only one Type?
+ box.prop(props, 'startAngle')
+ box.prop(props, 'endAngle')
+ box.prop(props, 'innerRadius') # doesn't seem to do anything
+ box.prop(props, 'outerRadius')
+ if props.GalloreType == 'Cogwheel':
+ box.prop(props, 'teeth')
+ box.prop(props, 'bevel')
+ box.prop(props, 'innerRadius')
+ box.prop(props, 'middleRadius')
+ box.prop(props, 'outerRadius')
+ if props.GalloreType == 'Nsided':
+ box.prop(props, 'Nsides')
+ box.prop(props, 'outerRadius', text='Radius')
+ '''
+ if props.GalloreType == 'Splat':
+ box.prop(props, 'splatSides')
+ box.prop(props, 'outerRadius')
+ box.prop(props, 'splatScale')
+ box.prop(props, 'seed')
+ box.prop(props, 'basis')
+ '''
+ if props.GalloreType == 'Helix':
+ box.prop(props, 'helixPoints')
+ box.prop(props, 'helixHeight')
+ box.prop(props, 'helixWidth')
+ box.prop(props, 'helixStart')
+ box.prop(props, 'helixEnd')
+ box.prop(props, 'helix_a')
+ box.prop(props, 'helix_b')
+ if props.GalloreType == 'Cycloid':
+ box.prop(props, 'cycloPoints')
+ #box.prop(props, 'cycloType') # needs the other types first
+ box.prop(props, 'cycloStart')
+ box.prop(props, 'cycloEnd')
+ box.prop(props, 'cyclo_a')
+ box.prop(props, 'cyclo_b')
+ box.prop(props, 'cyclo_d')
+
+ col = layout.column()
+ col.label(text="Output Curve Type")
+ row = layout.row()
+ row.prop(props, 'outputType', expand=True)
+ col = layout.column()
+ col.label(text="Curve Options")
+
+ # output options
+ box = layout.box()
+ if props.outputType == 'NURBS':
+ box.row().prop(props, 'shape', expand=True)
+ box.prop(props, 'cyclic_u')
+ #box.prop(props, 'endp_u')
+ box.prop(props, 'order_u')
+
+ if props.outputType == 'POLY':
+ box.row().prop(props, 'shape', expand=True)
+ box.prop(props, 'cyclic_u')
+
+ if props.outputType == 'BEZIER':
+ box.row().prop(props, 'shape', expand=True)
+ box.row().prop(props, 'handleType', expand=True)
+ box.prop(props, 'cyclic_u')
+
+
+ ##### POLL #####
+ def poll(self, context):
+ return context.scene != None
+
+ ##### EXECUTE #####
+ def execute(self, context):
+ # turn off undo
+ undo = bpy.context.user_preferences.edit.global_undo
+ bpy.context.user_preferences.edit.global_undo = False
+
+ props = self.properties
+ if props.GalloreType in ['Helix', 'Cycloid']:
+ props.shape = '3D'
+ if props.GalloreType in ['Helix']:
+ props.cyclic_u = False
+
+ # Options
+ options = [
+ # general properties
+ props.outputType, #0
+ props.GalloreType, #1
+ # ProfileCurve properties
+ props.ProfileCurveType, #2
+ props.ProfileCurvevar1, #3
+ props.ProfileCurvevar2, #4
+ # MiscCurve properties
+ props.MiscCurveType, #5
+ props.MiscCurvevar1, #6
+ props.MiscCurvevar2, #7
+ props.MiscCurvevar3, #8
+ # Common properties
+ props.innerRadius, #9
+ props.middleRadius, #10
+ props.outerRadius, #11
+ # Flower properties
+ props.petals, #12
+ props.petalWidth, #13
+ # Star properties
+ props.starPoints, #14
+ props.starTwist, #15
+ # Arc properties
+ props.arcSides, #16
+ props.startAngle, #17
+ props.endAngle, #18
+ props.arcType, #19
+ # Cogwheel properties
+ props.teeth, #20
+ props.bevel, #21
+ # Nsided property
+ props.Nsides, #22
+ # Splat properties
+ props.splatSides, #23
+ props.splatScale, #24
+ props.seed, #25
+ props.basis, #26
+ # Helix properties
+ props.helixPoints, #27
+ props.helixHeight, #28
+ props.helixStart, #29
+ props.helixEnd, #30
+ props.helixWidth, #31
+ props.helix_a, #32
+ props.helix_b, #33
+ # Cycloid properties
+ props.cycloPoints, #34
+ props.cyclo_d, #35
+ props.cycloType, #36
+ props.cyclo_a, #37
+ props.cyclo_b, #38
+ props.cycloStart, #39
+ props.cycloEnd #40
+ ]
+
+ # Curve options
+ curveOptions = [
+ props.shape, #0
+ props.cyclic_u, #1
+ props.endp_u, #2
+ props.order_u, #4
+ props.handleType #5
+ ]
+
+ # main function
+ main(context, options, curveOptions, self.align_matrix)
+
+ # restore pre operator undo state
+ bpy.context.user_preferences.edit.global_undo = undo
+
+ return {'FINISHED'}
+
+ ##### INVOKE #####
+ def invoke(self, context, event):
+ # store creation_matrix
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+
+ return {'FINISHED'}
+
+################################################################################
+##### REGISTER #####
+
+Curveaceous_galore_button = (lambda self, context: self.layout.operator
+ (Curveaceous_galore.bl_idname, text="curvatures gallore", icon="PLUGIN"))
+
+classes = [
+Curveaceous_galore
+ ]
+
+def register():
+ register = bpy.types.register
+ for cls in classes:
+ register(cls)
+
+ bpy.types.INFO_MT_curve_add.append(Curveaceous_galore_button)
+
+def unregister():
+ unregister = bpy.types.unregister
+ for cls in classes:
+ unregister(cls)
+
+ bpy.types.INFO_MT_curve_add.remove(Curveaceous_galore_button)
+
+if __name__ == "__main__":
+ register()
diff --git a/add_curve_torus_knots.py b/add_curve_torus_knots.py
new file mode 100644
index 00000000..9dc6ac8e
--- /dev/null
+++ b/add_curve_torus_knots.py
@@ -0,0 +1,363 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+bl_addon_info = {
+ 'name': 'Add Curve: Torus Knots',
+ 'author': 'testscreenings',
+ 'version': '0.1',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Curve',
+ 'description': 'Adds many types of knots',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Curve/Torus_Knot',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22403&group_id=153&atid=469',
+ 'category': 'Add Curve'}
+
+
+##------------------------------------------------------------
+#### import modules
+import bpy
+from bpy.props import *
+from mathutils import *
+from math import *
+
+##------------------------------------------------------------
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = TranslationMatrix(context.scene.cursor_location)
+ obj_align = context.user_preferences.edit.object_align
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ rot = context.space_data.region_3d.view_matrix.rotation_part().invert().resize4x4()
+ else:
+ rot = Matrix()
+ align_matrix = loc * rot
+ return align_matrix
+
+##------------------------------------------------------------
+#### Curve creation functions
+
+# get array of vertcoordinates acording to splinetype
+def vertsToPoints(Verts):
+ vertArray = []
+
+ for v in Verts:
+ vertArray += v
+ vertArray.append(1) #for nurbs w=1
+
+ return vertArray
+
+# create new CurveObject from vertarray and splineType
+def createCurve(vertArray, GEO, align_matrix):
+ # options to vars
+ splineType = 'NURBS'
+ name = 'Torus_Knot'
+
+ # create curve
+ scene = bpy.context.scene
+ newCurve = bpy.data.curves.new(name, type = 'CURVE') # curvedatablock
+ newSpline = newCurve.splines.new(type = splineType) # spline
+
+ # create spline from vertarray
+ newSpline.points.add(int(len(vertArray)*0.25 - 1))
+ newSpline.points.foreach_set('co', vertArray)
+ newSpline.endpoint_u = True
+
+ # Curve settings
+ newCurve.dimensions = '3D'
+ newSpline.cyclic_u = True
+ newSpline.endpoint_u = True
+ newSpline.order_u = 4
+
+ # GEO Options
+ surf = GEO[0]
+ bDepth = GEO[1]
+ bRes = GEO[2]
+ extrude = GEO[3]
+ width = GEO[4]
+ res = GEO[5]
+
+ if surf:
+ newCurve.bevel_depth = bDepth
+ newCurve.bevel_resolution = bRes
+ newCurve.front = False
+ newCurve.back = False
+ newCurve.extrude = extrude
+ newCurve.width = width
+ newCurve.resolution_u = res
+
+ # create object with newCurve
+ new_obj = bpy.data.objects.new(name, newCurve) # object
+ scene.objects.link(new_obj) # place in active scene
+ new_obj.select = True # set as selected
+ scene.objects.active = new_obj # set as active
+ new_obj.matrix_world = align_matrix # apply matrix
+
+ return
+
+########################################################################
+####################### Knot Definitions ###############################
+########################################################################
+
+#### TORUS KNOT
+def Torus_Knot_Curve(p=2, q=3, w=1, res=24, formula=0, h=1, u=1 ,v=1, rounds=2):
+ newPoints = []
+ angle = 2*rounds
+ step = angle/(res-1)
+ scale = h
+ height = w
+
+ for i in range(res-1):
+ t = ( i*step*pi)
+
+ x = (2 * scale + cos((q*t)/p*v)) * cos(t * u)
+ y = (2 * scale + cos((q*t)/p*v)) * sin(t * u)
+ z = sin(q*t/p) * height
+
+ newPoints.append([x,y,z])
+
+ return newPoints
+
+##------------------------------------------------------------
+# Main Function
+def main(context, param, GEO, options, align_matrix):
+ # deselect all objects
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # options
+ splineType = 'NURBS'
+
+
+ # get verts
+ verts = Torus_Knot_Curve(param[0], param[1], param[2], param[3], param[4],
+ param[5], param[6], param[7], param[8])
+
+ # turn verts into array
+ vertArray = vertsToPoints(verts)
+
+ # create object
+ createCurve(vertArray, GEO, align_matrix)
+
+ return
+
+class torus_knot_plus(bpy.types.Operator):
+ ''''''
+ bl_idname = "torus_knot_plus"
+ bl_label = "Torus Knot +"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = "adds many types of knots"
+
+ # align_matrix for the invoke
+ align_matrix = Matrix()
+
+ #### general options
+ options_plus = BoolProperty(name="plus options",
+ default=False,
+ description="Show more options (the plus part).")
+
+ #### GEO Options
+ geo_surf = BoolProperty(name="Surface",
+ default=True)
+ geo_bDepth = FloatProperty(name="bevel",
+ default=0.08,
+ min=0, soft_min=0)
+ geo_bRes = IntProperty(name="bevel res",
+ default=2,
+ min=0, soft_min=0,
+ max=4, soft_max=4)
+ geo_extrude = FloatProperty(name="extrude",
+ default=0.0,
+ min=0, soft_min=0)
+ geo_width = FloatProperty(name="width",
+ default=1.0,
+ min=0, soft_min=0)
+ geo_res = IntProperty(name="resolution",
+ default=12,
+ min=1, soft_min=1)
+
+
+ #### Parameters
+ torus_res = IntProperty(name="Resoulution",
+ default=200,
+ min=3, soft_min=3,
+ description='Resolution, Number of controlverticies.')
+ torus_p = IntProperty(name="p",
+ default=2,
+ min=1, soft_min=1,
+ #max=1, soft_max=1,
+ description="p")
+ torus_q = IntProperty(name="q",
+ default=3,
+ min=1, soft_min=1,
+ #max=1, soft_max=1,
+ description="q")
+ torus_w = FloatProperty(name="Height",
+ default=1,
+ #min=0, soft_min=0,
+ #max=1, soft_max=1,
+ description="Height in Z")
+ torus_h = FloatProperty(name="Scale",
+ default=1,
+ #min=0, soft_min=0,
+ #max=1, soft_max=1,
+ description="Scale, in XY")
+ torus_u = IntProperty(name="u",
+ default=1,
+ min=1, soft_min=1,
+ #max=1, soft_max=1,
+ description="u")
+ torus_v = IntProperty(name="v",
+ default=1,
+ min=1, soft_min=1,
+ #max=1, soft_max=1,
+ description="v")
+ torus_formula = IntProperty(name="Variation",
+ default=0,
+ min=0, soft_min=0,
+ max=10, soft_max=10)
+ torus_rounds = IntProperty(name="Rounds",
+ default=2,
+ min=1, soft_min=1,
+ #max=1, soft_max=1,
+ description="Rounds")
+
+ ##### DRAW #####
+ def draw(self, context):
+ props = self.properties
+ layout = self.layout
+
+ # general options
+ col = layout.column()
+ #col.prop(props, 'KnotType') waits for more knottypes
+ col.label(text="Torus Knot Parameters")
+
+ # Parameters
+ box = layout.box()
+ box.prop(props, 'torus_res')
+ box.prop(props, 'torus_w')
+ box.prop(props, 'torus_h')
+ box.prop(props, 'torus_p')
+ box.prop(props, 'torus_q')
+ box.prop(props, 'options_plus')
+ if props.options_plus:
+ box.prop(props, 'torus_u')
+ box.prop(props, 'torus_v')
+ box.prop(props, 'torus_rounds')
+
+ # surface Options
+ col = layout.column()
+ col.label(text="Geometry Options")
+ box = layout.box()
+ box.prop(props, 'geo_surf')
+ if props.geo_surf:
+ box.prop(props, 'geo_bDepth')
+ box.prop(props, 'geo_bRes')
+ box.prop(props, 'geo_extrude')
+ #box.prop(props, 'geo_width') # not really good
+ box.prop(props, 'geo_res')
+
+ ##### POLL #####
+ def poll(self, context):
+ return context.scene != None
+
+ ##### EXECUTE #####
+ def execute(self, context):
+ # turn off undo
+ undo = bpy.context.user_preferences.edit.global_undo
+ bpy.context.user_preferences.edit.global_undo = False
+
+ props = self.properties
+
+ if not props.options_plus:
+ props.torus_rounds = props.torus_p
+
+ # Parameters
+ param = [
+ props.torus_p, #0
+ props.torus_q, #1
+ props.torus_w, #2
+ props.torus_res, #3
+ props.torus_formula, #4
+ props.torus_h, #5
+ props.torus_u, #6
+ props.torus_v, #7
+ props.torus_rounds #8
+ ]
+
+ # GEO Options
+ GEO = [
+ props.geo_surf, #0
+ props.geo_bDepth, #1
+ props.geo_bRes, #2
+ props.geo_extrude, #3
+ props.geo_width, #4
+ props.geo_res #5
+ ]
+
+ # Options
+ options = [
+ # general properties
+ ]
+
+
+ # main function
+ main(context, param, GEO, options, self.align_matrix)
+
+ # restore pre operator undo state
+ bpy.context.user_preferences.edit.global_undo = undo
+
+ return {'FINISHED'}
+
+ ##### INVOKE #####
+ def invoke(self, context, event):
+ # store creation_matrix
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+
+ return {'FINISHED'}
+
+################################################################################
+##### REGISTER #####
+
+torus_knot_plus_button = (lambda self, context: self.layout.operator
+ (torus_knot_plus.bl_idname, text="Torus Knot +", icon="PLUGIN"))
+
+classes = [
+torus_knot_plus
+ ]
+
+def register():
+ register = bpy.types.register
+ for cls in classes:
+ register(cls)
+
+ bpy.types.INFO_MT_curve_add.append(torus_knot_plus_button)
+
+def unregister():
+ unregister = bpy.types.unregister
+ for cls in classes:
+ unregister(cls)
+
+ bpy.types.INFO_MT_curve_add.remove(torus_knot_plus_button)
+
+if __name__ == "__main__":
+ register()
diff --git a/add_mesh_3d_function_surface.py b/add_mesh_3d_function_surface.py
new file mode 100644
index 00000000..79a01ae4
--- /dev/null
+++ b/add_mesh_3d_function_surface.py
@@ -0,0 +1,648 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Z Function Surface
+
+This script lets the user create a surface where the z coordinate
+is a function of the x and y coordinates.
+
+ z = f(x,y)
+
+X,Y,Z Function Surface
+
+This script lets the user create a surface where the x, y and z
+coordinates are defiend by a function.
+
+ x = f(u,v)
+ y = f(u,v)
+ z = f(u,v)
+
+Usage:
+You have to activated the script in the "Add-Ons" tab (user preferences).
+The functionality can then be accessed via the
+"Add Mesh" -> "Z Function Surface"
+and
+"Add Mesh" -> "X,Y,Z Function Surface"
+menu.
+
+Version history:
+v0.3.5 - createFaces can now "Flip" faces and create fan/star like faces.
+v0.3.4 - Updated store_recall_properties, apply_object_align
+ and create_mesh_object.
+ Changed how recall data is stored.
+v0.3.3 - API change Mathutils -> mathutils (r557)
+v0.3.2 - Various fixes&streamlining by ideasman42/Campbell Barton.
+ r544 Compile expressions for faster execution
+ r544 Use operator reports for errors too
+ r544 Avoid type checks by converting to a float, errors
+ converting to a float are reported too.
+ Fixed an error Campbell overlooked (appending tuples to an
+ array, not single values) Thamnks for the report wild_doogy.
+ Added 'description' field, updated 'wiki_url'.
+ Made the script PEP8 compatible again.
+v0.3.1 - Use hidden "edit" property for "recall" operator.
+ Bugfix: Z Function was mixing up div_x and div_y
+v0.3 - X,Y,Z Function Surface (by Ed Mackey & tuga3d).
+ Renamed old function to "Z Function Surface".
+ Align the geometry to the view if the user preference says so.
+ Store recall properties in newly created object.
+v0.2.3 - Use bl_addon_info for Add-On information.
+v0.2.2 - Fixed Add-On registration text.
+v0.2.1 - Fixed some new API stuff.
+ Mainly we now have the register/unregister functions.
+ Also the new() function for objects now accepts a mesh object.
+ Changed the script so it can be managed from the "Add-Ons" tab
+ in the user preferences.
+ Added dummy "PLUGIN" icon.
+ Corrected FSF address.
+ Clean up of tooltips.
+v0.2 - Added security check for eval() function
+ Check return value of eval() for complex numbers.
+v0.1.1 - Use 'CANCELLED' return value when failing.
+ Updated web links.
+v0.1 - Initial revision.
+
+More Links:
+http://gitorious.org/blender-scripts/blender-3d-function-surface
+http://blenderartists.org/forum/showthread.php?t=179043
+
+"""
+
+
+bl_addon_info = {
+ 'name': 'Add Mesh: 3D Function Surfaces',
+ 'author': 'Buerbaum Martin (Pontiac)',
+ 'version': '0.3.5',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Mesh > Z Function Surface &' \
+ ' XYZ Function Surface',
+ 'description': 'Create Objects using Math Formulas',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Add_Mesh/Add_3d_Function_Surface',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21444&group_id=153&atid=469',
+ 'category': 'Add Mesh'}
+
+import bpy
+from mathutils import *
+from math import *
+from bpy.props import *
+
+# List of safe functions for eval()
+safe_list = ['math', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'cosh',
+ 'degrees', 'e', 'exp', 'fabs', 'floor', 'fmod', 'frexp', 'hypot',
+ 'ldexp', 'log', 'log10', 'modf', 'pi', 'pow', 'radians',
+ 'sin', 'sinh', 'sqrt', 'tan', 'tanh']
+
+# Use the list to filter the local namespace
+safe_dict = dict([(k, globals().get(k, None)) for k in safe_list])
+
+
+# Stores the values of a list of properties and the
+# operator id in a property group ('recall_op') inside the object.
+# Could (in theory) be used for non-objects.
+# Note: Replaces any existing property group with the same name!
+# ob ... Object to store the properties in.
+# op ... The operator that should be used.
+# op_args ... A dictionary with valid Blender
+# properties (operator arguments/parameters).
+
+
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = TranslationMatrix(context.scene.cursor_location)
+ obj_align = context.user_preferences.edit.object_align
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ rot = context.space_data.region_3d.view_matrix.rotation_part().invert().resize4x4()
+ else:
+ rot = Matrix()
+ align_matrix = loc * rot
+ return align_matrix
+
+
+# Create a new mesh (object) from verts/edges/faces.
+# verts/edges/faces ... List of vertices/edges/faces for the
+# new mesh (as used in from_pydata).
+# name ... Name of the new mesh (& object).
+# edit ... Replace existing mesh data.
+# Note: Using "edit" will destroy/delete existing mesh data.
+def create_mesh_object(context, verts, edges, faces, name, edit, align_matrix):
+ scene = context.scene
+ obj_act = scene.objects.active
+
+ # Can't edit anything, unless we have an active obj.
+ if edit and not obj_act:
+ return None
+
+ # Create new mesh
+ mesh = bpy.data.meshes.new(name)
+
+ # Make a mesh from a list of verts/edges/faces.
+ mesh.from_pydata(verts, edges, faces)
+
+ # Update mesh geometry after adding stuff.
+ mesh.update()
+
+ # Deselect all objects.
+ bpy.ops.object.select_all(action='DESELECT')
+
+ if edit:
+ # Replace geometry of existing object
+
+ # Use the active obj and select it.
+ ob_new = obj_act
+ ob_new.select = True
+
+ if obj_act.mode == 'OBJECT':
+ # Get existing mesh datablock.
+ old_mesh = ob_new.data
+
+ # Set object data to nothing
+ ob_new.data = None
+
+ # Clear users of existing mesh datablock.
+ old_mesh.user_clear()
+
+ # Remove old mesh datablock if no users are left.
+ if (old_mesh.users == 0):
+ bpy.data.meshes.remove(old_mesh)
+
+ # Assign new mesh datablock.
+ ob_new.data = mesh
+
+ else:
+ # Create new object
+ ob_new = bpy.data.objects.new(name, mesh)
+
+ # Link new object to the given scene and select it.
+ scene.objects.link(ob_new)
+ ob_new.select = True
+
+ # Place the object at the 3D cursor location.
+ # apply viewRotaion
+ ob_new.matrix_world = align_matrix
+
+ if obj_act and obj_act.mode == 'EDIT':
+ if not edit:
+ # We are in EditMode, switch to ObjectMode.
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Select the active object as well.
+ obj_act.select = True
+
+ # Apply location of new object.
+ scene.update()
+
+ # Join new object into the active.
+ bpy.ops.object.join()
+
+ # Switching back to EditMode.
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ ob_new = obj_act
+
+ else:
+ # We are in ObjectMode.
+ # Make the new object the active one.
+ scene.objects.active = ob_new
+
+ return ob_new
+
+
+# A very simple "bridge" tool.
+# Connects two equally long vertex rows with faces.
+# Returns a list of the new faces (list of lists)
+#
+# vertIdx1 ... First vertex list (list of vertex indices).
+# vertIdx2 ... Second vertex list (list of vertex indices).
+# closed ... Creates a loop (first & last are closed).
+# flipped ... Invert the normal of the face(s).
+#
+# Note: You can set vertIdx1 to a single vertex index to create
+# a fan/star of faces.
+# Note: If both vertex idx list are the same length they have
+# to have at least 2 vertices.
+def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
+ faces = []
+
+ if not vertIdx1 or not vertIdx2:
+ return None
+
+ if len(vertIdx1) < 2 and len(vertIdx2) < 2:
+ return None
+
+ fan = False
+ if (len(vertIdx1) != len(vertIdx2)):
+ if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
+ fan = True
+ else:
+ return None
+
+ total = len(vertIdx2)
+
+ if closed:
+ # Bridge the start with the end.
+ if flipped:
+ face = [
+ vertIdx1[0],
+ vertIdx2[0],
+ vertIdx2[total - 1]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ faces.append(face)
+
+ else:
+ face = [vertIdx2[0], vertIdx1[0]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ face.append(vertIdx2[total - 1])
+ faces.append(face)
+
+ # Bridge the rest of the faces.
+ for num in range(total - 1):
+ if flipped:
+ if fan:
+ face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx2[num], vertIdx1[num],
+ vertIdx1[num + 1], vertIdx2[num + 1]]
+ faces.append(face)
+ else:
+ if fan:
+ face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx1[num], vertIdx2[num],
+ vertIdx2[num + 1], vertIdx1[num + 1]]
+ faces.append(face)
+
+ return faces
+
+
+class AddZFunctionSurface(bpy.types.Operator):
+ '''Add a surface defined defined by a function z=f(x,y)'''
+ bl_idname = "mesh.primitive_z_function_surface"
+ bl_label = "Add Z Function Surface"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+ equation = StringProperty(name="Z Equation",
+ description="Equation for z=f(x,y)",
+ default="1 - ( x**2 + y**2 )")
+
+ div_x = IntProperty(name="X Subdivisions",
+ description="Number of vertices in x direction.",
+ default=16,
+ min=3,
+ max=256)
+ div_y = IntProperty(name="Y Subdivisions",
+ description="Number of vertices in y direction.",
+ default=16,
+ min=3,
+ max=256)
+
+ size_x = FloatProperty(name="X Size",
+ description="Size of the x axis.",
+ default=2.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ size_y = FloatProperty(name="Y Size",
+ description="Size of the y axis.",
+ default=2.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ align_matrix = Matrix()
+
+ def execute(self, context):
+ edit = self.properties.edit
+ equation = self.properties.equation
+ div_x = self.properties.div_x
+ div_y = self.properties.div_y
+ size_x = self.properties.size_x
+ size_y = self.properties.size_y
+
+ verts = []
+ faces = []
+
+ delta_x = size_x / float(div_x - 1)
+ delta_y = size_y / float(div_y - 1)
+ start_x = -(size_x / 2.0)
+ start_y = -(size_y / 2.0)
+
+ edgeloop_prev = []
+
+ try:
+ expr_args = (
+ compile(equation, __file__, 'eval'),
+ {"__builtins__": None},
+ safe_dict)
+ except:
+ import traceback
+ self.report({'ERROR'}, "Error parsing expression: "
+ + traceback.format_exc(limit=1))
+ return {'CANCELLED'}
+
+ for row_x in range(div_x):
+ edgeloop_cur = []
+ x = start_x + row_x * delta_x
+
+ for row_y in range(div_y):
+ y = start_y + row_y * delta_y
+ z = 0.0
+
+ safe_dict['x'] = x
+ safe_dict['y'] = y
+
+ # Try to evaluate the equation.
+ try:
+ z = float(eval(*expr_args))
+ except:
+ import traceback
+ self.report({'ERROR'}, "Error evaluating expression: "
+ + traceback.format_exc(limit=1))
+ return {'CANCELLED'}
+
+ edgeloop_cur.append(len(verts))
+ verts.append((x, y, z))
+
+ if len(edgeloop_prev) > 0:
+ faces_row = createFaces(edgeloop_prev, edgeloop_cur)
+ faces.extend(faces_row)
+
+ edgeloop_prev = edgeloop_cur
+
+ obj = create_mesh_object(context, verts, [], faces, "Z Function", edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+def xyz_function_surface_faces(self, x_eq, y_eq, z_eq,
+ range_u_min, range_u_max, range_u_step, wrap_u,
+ range_v_min, range_v_max, range_v_step, wrap_v):
+
+ verts = []
+ faces = []
+
+ uStep = (range_u_max - range_u_min) / range_u_step
+ vStep = (range_v_max - range_v_min) / range_v_step
+
+ uRange = range_u_step
+ if range_u_step == 0:
+ uRange = uRange + 1
+
+ vRange = range_v_step
+ if range_v_step == 0:
+ vRange = vRange + 1
+
+ try:
+ expr_args_x = (
+ compile(x_eq, __file__.replace(".py", "_x.py"), 'eval'),
+ {"__builtins__": None},
+ safe_dict)
+ expr_args_y = (
+ compile(y_eq, __file__.replace(".py", "_y.py"), 'eval'),
+ {"__builtins__": None},
+ safe_dict)
+ expr_args_z = (
+ compile(z_eq, __file__.replace(".py", "_z.py"), 'eval'),
+ {"__builtins__": None},
+ safe_dict)
+ except:
+ import traceback
+ self.report({'ERROR'}, "Error parsing expression: "
+ + traceback.format_exc(limit=1))
+ return [], []
+
+ for vN in range(vRange):
+ v = range_v_min + (vN * vStep)
+
+ for uN in range(uRange):
+ u = range_u_min + (uN * uStep)
+
+ safe_dict['u'] = u
+ safe_dict['v'] = v
+
+ # Try to evaluate the equation.
+ try:
+ verts.append((
+ float(eval(*expr_args_x)),
+ float(eval(*expr_args_y)),
+ float(eval(*expr_args_z))))
+
+ except:
+ import traceback
+ self.report({'ERROR'}, "Error evaluating expression: "
+ + traceback.format_exc(limit=1))
+ return [], []
+
+ for vN in range(1, range_v_step + 1):
+ vThis = vN
+
+ if (vThis >= vRange):
+ if wrap_v:
+ vThis = 0
+ else:
+ continue
+
+ for uN in range(1, range_u_step + 1):
+ uThis = uN
+
+ if (uThis >= uRange):
+ if wrap_u:
+ uThis = 0
+ else:
+ continue
+
+ faces.append([(vThis * uRange) + uThis,
+ (vThis * uRange) + uN - 1,
+ ((vN - 1) * uRange) + uN - 1,
+ ((vN - 1) * uRange) + uThis])
+
+ return verts, faces
+
+
+# Original Script "Parametric.py" by Ed Mackey.
+# -> http://www.blinken.com/blender-plugins.php
+# Partly converted for Blender 2.5 by tuga3d.
+#
+# Sphere:
+# x = sin(2*pi*u)*sin(pi*v)
+# y = cos(2*pi*u)*sin(pi*v)
+# z = cos(pi*v)
+# u_min = v_min = 0
+# u_max = v_max = 1
+#
+# "Snail shell"
+# x = 1.2**v*(sin(u)**2 *sin(v))
+# y = 1.2**v*(sin(u)*cos(u))
+# z = 1.2**v*(sin(u)**2 *cos(v))
+# u_min = 0
+# u_max = pi
+# v_min = -pi/4,
+# v max = 5*pi/2
+class AddXYZFunctionSurface(bpy.types.Operator):
+ '''Add a surface defined defined by 3 functions:''' \
+ + ''' x=f(u,v), y=f(u,v) and z=f(u,v)'''
+ bl_idname = "mesh.primitive_xyz_function_surface"
+ bl_label = "Add X,Y,Z Function Surface"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+ x_eq = StringProperty(name="X Equation",
+ description="Equation for x=f(u,v)",
+ default="1.2**v*(sin(u)**2 *sin(v))")
+
+ y_eq = StringProperty(name="Y Equation",
+ description="Equation for y=f(u,v)",
+ default="1.2**v*(sin(u)*cos(u))")
+
+ z_eq = StringProperty(name="Z Equation",
+ description="Equation for z=f(u,v)",
+ default="1.2**v*(sin(u)**2 *cos(v))")
+
+ range_u_min = FloatProperty(name="U min",
+ description="Minimum U value. Lower boundary of U range.",
+ min=-100.00,
+ max=0.00,
+ default=0.00)
+
+ range_u_max = FloatProperty(name="U max",
+ description="Maximum U value. Upper boundary of U range.",
+ min=0.00,
+ max=100.00,
+ default=pi)
+
+ range_u_step = IntProperty(name="U step",
+ description="U Subdivisions",
+ min=1,
+ max=1024,
+ default=32)
+
+ wrap_u = BoolProperty(name="U wrap",
+ description="U Wrap around",
+ default=True)
+
+ range_v_min = FloatProperty(name="V min",
+ description="Minimum V value. Lower boundary of V range.",
+ min=-100.00,
+ max=0.00,
+ default=-pi / 4)
+
+ range_v_max = FloatProperty(name="V max",
+ description="Maximum V value. Upper boundary of V range.",
+ min=0.00,
+ max=100.00,
+ default=5 * pi / 2)
+
+ range_v_step = IntProperty(name="V step",
+ description="V Subdivisions",
+ min=1,
+ max=1024,
+ default=32)
+
+ wrap_v = BoolProperty(name="V wrap",
+ description="V Wrap around",
+ default=False)
+
+ align_matrix = Matrix()
+
+ def execute(self, context):
+ props = self.properties
+
+ verts, faces = xyz_function_surface_faces(
+ self,
+ props.x_eq,
+ props.y_eq,
+ props.z_eq,
+ props.range_u_min,
+ props.range_u_max,
+ props.range_u_step,
+ props.wrap_u,
+ props.range_v_min,
+ props.range_v_max,
+ props.range_v_step,
+ props.wrap_v)
+
+ if not verts:
+ return {'CANCELLED'}
+
+ obj = create_mesh_object(context, verts, [], faces,
+ "XYZ Function", props.edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+
+################################
+import space_info
+
+# Define "3D Function Surface" menu
+menu_func_z = (lambda self, context: self.layout.operator(
+ AddZFunctionSurface.bl_idname,
+ text="Z Function Surface",
+ icon="PLUGIN"))
+menu_func_xyz = (lambda self, context: self.layout.operator(
+ AddXYZFunctionSurface.bl_idname,
+ text="X,Y,Z Function Surface",
+ icon="PLUGIN"))
+
+
+def register():
+ # Register the operators/menus.
+ bpy.types.register(AddZFunctionSurface)
+ bpy.types.register(AddXYZFunctionSurface)
+
+ # Add menus to the "Add Mesh" menu
+ space_info.INFO_MT_mesh_add.append(menu_func_z)
+ space_info.INFO_MT_mesh_add.append(menu_func_xyz)
+
+
+def unregister():
+ # Unregister the operators/menus.
+ bpy.types.unregister(AddZFunctionSurface)
+ bpy.types.unregister(AddXYZFunctionSurface)
+
+ # Remove menus from the "Add Mesh" menu.
+ space_info.INFO_MT_mesh_add.remove(menu_func_z)
+ space_info.INFO_MT_mesh_add.remove(menu_func_xyz)
+
+if __name__ == "__main__":
+ register()
diff --git a/add_mesh_extras.py b/add_mesh_extras.py
new file mode 100644
index 00000000..ccf74a4a
--- /dev/null
+++ b/add_mesh_extras.py
@@ -0,0 +1,791 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_addon_info = {
+ 'name': 'Add Mesh: Extras',
+ 'author': 'Pontiac, Fourmadmen, meta-androcto',
+ 'version': '0.3',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Mesh > Extras',
+ 'description': 'Adds Star, Wedge, Sqorus & Spindle objects.',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Add_Mesh/Add_Extra',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22457&group_id=153&atid=469',
+ 'category': 'Add Mesh'}
+
+import bpy
+from mathutils import *
+from math import *
+from bpy.props import *
+
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = TranslationMatrix(context.scene.cursor_location)
+ obj_align = context.user_preferences.edit.object_align
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ rot = context.space_data.region_3d.view_matrix.rotation_part().invert().resize4x4()
+ else:
+ rot = Matrix()
+ align_matrix = loc * rot
+ return align_matrix
+
+# Create a new mesh (object) from verts/edges/faces.
+# verts/edges/faces ... List of vertices/edges/faces for the
+# new mesh (as used in from_pydata).
+# name ... Name of the new mesh (& object).
+# edit ... Replace existing mesh data.
+# Note: Using "edit" will destroy/delete existing mesh data.
+def create_mesh_object(context, verts, edges, faces, name, edit, align_matrix):
+ scene = context.scene
+ obj_act = scene.objects.active
+
+ # Can't edit anything, unless we have an active obj.
+ if edit and not obj_act:
+ return None
+
+ # Create new mesh
+ mesh = bpy.data.meshes.new(name)
+
+ # Make a mesh from a list of verts/edges/faces.
+ mesh.from_pydata(verts, edges, faces)
+
+ # Update mesh geometry after adding stuff.
+ mesh.update()
+
+ # Deselect all objects.
+ bpy.ops.object.select_all(action='DESELECT')
+
+ if edit:
+ # Replace geometry of existing object
+
+ # Use the active obj and select it.
+ ob_new = obj_act
+ ob_new.select = True
+
+ if obj_act.mode == 'OBJECT':
+ # Get existing mesh datablock.
+ old_mesh = ob_new.data
+
+ # Set object data to nothing
+ ob_new.data = None
+
+ # Clear users of existing mesh datablock.
+ old_mesh.user_clear()
+
+ # Remove old mesh datablock if no users are left.
+ if (old_mesh.users == 0):
+ bpy.data.meshes.remove(old_mesh)
+
+ # Assign new mesh datablock.
+ ob_new.data = mesh
+
+ else:
+ # Create new object
+ ob_new = bpy.data.objects.new(name, mesh)
+
+ # Link new object to the given scene and select it.
+ scene.objects.link(ob_new)
+ ob_new.select = True
+
+ # Place the object at the 3D cursor location.
+ # apply viewRotaion
+ ob_new.matrix_world = align_matrix
+
+ if obj_act and obj_act.mode == 'EDIT':
+ if not edit:
+ # We are in EditMode, switch to ObjectMode.
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Select the active object as well.
+ obj_act.select = True
+
+ # Apply location of new object.
+ scene.update()
+
+ # Join new object into the active.
+ bpy.ops.object.join()
+
+ # Switching back to EditMode.
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ ob_new = obj_act
+
+ else:
+ # We are in ObjectMode.
+ # Make the new object the active one.
+ scene.objects.active = ob_new
+
+ return ob_new
+
+
+# A very simple "bridge" tool.
+# Connects two equally long vertex rows with faces.
+# Returns a list of the new faces (list of lists)
+#
+# vertIdx1 ... First vertex list (list of vertex indices).
+# vertIdx2 ... Second vertex list (list of vertex indices).
+# closed ... Creates a loop (first & last are closed).
+# flipped ... Invert the normal of the face(s).
+#
+# Note: You can set vertIdx1 to a single vertex index to create
+# a fan/star of faces.
+# Note: If both vertex idx list are the same length they have
+# to have at least 2 vertices.
+def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
+ faces = []
+
+ if not vertIdx1 or not vertIdx2:
+ return None
+
+ if len(vertIdx1) < 2 and len(vertIdx2) < 2:
+ return None
+
+ fan = False
+ if (len(vertIdx1) != len(vertIdx2)):
+ if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
+ fan = True
+ else:
+ return None
+
+ total = len(vertIdx2)
+
+ if closed:
+ # Bridge the start with the end.
+ if flipped:
+ face = [
+ vertIdx1[0],
+ vertIdx2[0],
+ vertIdx2[total - 1]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ faces.append(face)
+
+ else:
+ face = [vertIdx2[0], vertIdx1[0]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ face.append(vertIdx2[total - 1])
+ faces.append(face)
+
+ # Bridge the rest of the faces.
+ for num in range(total - 1):
+ if flipped:
+ if fan:
+ face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx2[num], vertIdx1[num],
+ vertIdx1[num + 1], vertIdx2[num + 1]]
+ faces.append(face)
+ else:
+ if fan:
+ face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx1[num], vertIdx2[num],
+ vertIdx2[num + 1], vertIdx1[num + 1]]
+ faces.append(face)
+
+ return faces
+
+
+# @todo Clean up vertex&face creation process a bit.
+def add_sqorus(hole_size, subdivide):
+ verts = []
+ faces = []
+
+ size = 2.0
+
+ thickness = (size - hole_size) / 2.0
+ distances = [
+ -size / 2.0,
+ -size / 2.0 + thickness,
+ size / 2.0 - thickness,
+ size / 2.0]
+
+ if subdivide:
+ for i in range(4):
+ y = distances[i]
+
+ for j in range(4):
+ x = distances[j]
+
+ verts.append(Vector((x, y, size / 2.0)))
+ verts.append(Vector((x, y, -size / 2.0)))
+
+ # Top outer loop (vertex indices)
+ vIdx_out_up = [0, 2, 4, 6, 14, 22, 30, 28, 26, 24, 16, 8]
+ # Lower outer loop (vertex indices)
+ vIdx_out_low = [i + 1 for i in vIdx_out_up]
+
+ faces_outside = createFaces(vIdx_out_up, vIdx_out_low, closed=True)
+ faces.extend(faces_outside)
+
+ # Top inner loop (vertex indices)
+ vIdx_inner_up = [10, 12, 20, 18]
+
+ # Lower inner loop (vertex indices)
+ vIdx_inner_low = [i + 1 for i in vIdx_inner_up]
+
+ faces_inside = createFaces(vIdx_inner_up, vIdx_inner_low,
+ closed=True, flipped=True)
+ faces.extend(faces_inside)
+
+ row1_top = [0, 8, 16, 24]
+ row2_top = [i + 2 for i in row1_top]
+ row3_top = [i + 2 for i in row2_top]
+ row4_top = [i + 2 for i in row3_top]
+
+ faces_top1 = createFaces(row1_top, row2_top)
+ faces.extend(faces_top1)
+ faces_top2_side1 = createFaces(row2_top[:2], row3_top[:2])
+ faces.extend(faces_top2_side1)
+ faces_top2_side2 = createFaces(row2_top[2:], row3_top[2:])
+ faces.extend(faces_top2_side2)
+ faces_top3 = createFaces(row3_top, row4_top)
+ faces.extend(faces_top3)
+
+ row1_bot = [1, 9, 17, 25]
+ row2_bot = [i + 2 for i in row1_bot]
+ row3_bot = [i + 2 for i in row2_bot]
+ row4_bot = [i + 2 for i in row3_bot]
+
+ faces_bot1 = createFaces(row1_bot, row2_bot, flipped=True)
+ faces.extend(faces_bot1)
+ faces_bot2_side1 = createFaces(row2_bot[:2], row3_bot[:2],
+ flipped=True)
+ faces.extend(faces_bot2_side1)
+ faces_bot2_side2 = createFaces(row2_bot[2:], row3_bot[2:],
+ flipped=True)
+ faces.extend(faces_bot2_side2)
+ faces_bot3 = createFaces(row3_bot, row4_bot, flipped=True)
+ faces.extend(faces_bot3)
+
+ else:
+ # Do not subdivde outer faces
+
+ vIdx_out_up = []
+ vIdx_out_low = []
+ vIdx_in_up = []
+ vIdx_in_low = []
+
+ for i in range(4):
+ y = distances[i]
+
+ for j in range(4):
+ x = distances[j]
+
+ append = False
+ inner = False
+ # Outer
+ if (i in [0, 3] and j in [0, 3]):
+ append = True
+
+ # Inner
+ if (i in [1, 2] and j in [1, 2]):
+ append = True
+ inner = True
+
+ if append:
+ vert_up = len(verts)
+ verts.append(Vector((x, y, size / 2.0)))
+ vert_low = len(verts)
+ verts.append(Vector((x, y, -size / 2.0)))
+
+ if inner:
+ vIdx_in_up.append(vert_up)
+ vIdx_in_low.append(vert_low)
+
+ else:
+ vIdx_out_up.append(vert_up)
+ vIdx_out_low.append(vert_low)
+
+ # Flip last two vertices
+ vIdx_out_up = vIdx_out_up[:2] + list(reversed(vIdx_out_up[2:]))
+ vIdx_out_low = vIdx_out_low[:2] + list(reversed(vIdx_out_low[2:]))
+ vIdx_in_up = vIdx_in_up[:2] + list(reversed(vIdx_in_up[2:]))
+ vIdx_in_low = vIdx_in_low[:2] + list(reversed(vIdx_in_low[2:]))
+
+ # Create faces
+ faces_top = createFaces(vIdx_in_up, vIdx_out_up, closed=True)
+ faces.extend(faces_top)
+ faces_bottom = createFaces(vIdx_out_low, vIdx_in_low, closed=True)
+ faces.extend(faces_bottom)
+ faces_inside = createFaces(vIdx_in_low, vIdx_in_up, closed=True)
+ faces.extend(faces_inside)
+ faces_outside = createFaces(vIdx_out_up, vIdx_out_low, closed=True)
+ faces.extend(faces_outside)
+
+ return verts, faces
+
+
+def add_wedge(size_x, size_y, size_z):
+ verts = []
+ faces = []
+
+ size_x /= 2.0
+ size_y /= 2.0
+ size_z /= 2.0
+
+ vIdx_top = []
+ vIdx_bot = []
+
+ vIdx_top.append(len(verts))
+ verts.append(Vector((-size_x, -size_y, size_z)))
+ vIdx_bot.append(len(verts))
+ verts.append(Vector((-size_x, -size_y, -size_z)))
+
+ vIdx_top.append(len(verts))
+ verts.append(Vector((size_x, -size_y, size_z)))
+ vIdx_bot.append(len(verts))
+ verts.append(Vector((size_x, -size_y, -size_z)))
+
+ vIdx_top.append(len(verts))
+ verts.append(Vector((-size_x, size_y, size_z)))
+ vIdx_bot.append(len(verts))
+ verts.append(Vector((-size_x, size_y, -size_z)))
+
+ faces.append(vIdx_top)
+ faces.append(vIdx_bot)
+ faces_outside = createFaces(vIdx_top, vIdx_bot, closed=True)
+ faces.extend(faces_outside)
+
+ return verts, faces
+
+def add_spindle(segments, radius, height, cap_height):
+ verts = []
+ faces = []
+
+ tot_verts = segments * 2 + 2
+
+ half_height = height / 2.0
+
+ # Upper tip
+ idx_upper_tip = len(verts)
+ verts.append(Vector((0, 0, half_height + cap_height)))
+
+ # Lower tip
+ idx_lower_tip = len(verts)
+ verts.append(Vector((0.0, 0.0, -half_height - cap_height)))
+
+ upper_edgeloop = []
+ lower_edgeloop = []
+ for index in range(segments):
+ mtx = RotationMatrix(2.0 * pi * float(index) / segments, 3, 'Z')
+
+ # Calculate index & location of upper verte4x tip.
+ idx_up = len(verts)
+ upper_edgeloop.append(idx_up)
+ verts.append(Vector((radius, 0.0, half_height)) * mtx)
+
+ if height > 0:
+ idx_low = len(verts)
+ lower_edgeloop.append(idx_low)
+ verts.append(Vector((radius, 0.0, -half_height)) * mtx)
+
+ # Create faces for the upper tip.
+ tip_up_faces = createFaces([idx_upper_tip], upper_edgeloop,
+ closed=True, flipped=True)
+ faces.extend(tip_up_faces)
+
+ if height > 0:
+ # Create faces for the middle cylinder.
+ cyl_faces = createFaces(lower_edgeloop, upper_edgeloop, closed=True)
+ faces.extend(cyl_faces)
+
+ # Create faces for the lower tip.
+ tip_low_faces = createFaces([idx_lower_tip], lower_edgeloop,
+ closed=True)
+ faces.extend(tip_low_faces)
+
+ else:
+ # Skipping middle part/cylinder (height=0).
+
+ # Create faces for the lower tip.
+ tip_low_faces = createFaces([idx_lower_tip], upper_edgeloop,
+ closed=True)
+ faces.extend(tip_low_faces)
+
+ return verts, faces
+
+def add_star(points, outer_radius, inner_radius, height):
+ PI_2 = pi * 2
+ z_axis = (0, 0, 1)
+
+ verts = []
+ faces = []
+
+ segments = points * 2
+
+ half_height = height / 2.0
+
+ vert_idx_top = len(verts)
+ verts.append(Vector((0.0, 0.0, half_height)))
+
+ vert_idx_bottom = len(verts)
+ verts.append(Vector((0.0, 0.0, -half_height)))
+
+ edgeloop_top = []
+ edgeloop_bottom = []
+
+ for index in range(segments):
+ quat = Quaternion(z_axis, (index / segments) * PI_2)
+
+ if index % 2:
+ # Uneven
+ radius = outer_radius
+ else:
+ # Even
+ radius = inner_radius
+
+ edgeloop_top.append(len(verts))
+ vec = Vector((radius, 0, half_height)) * quat
+ verts.append(vec)
+
+ edgeloop_bottom.append(len(verts))
+ vec = Vector((radius, 0, -half_height)) * quat
+ verts.append(vec)
+
+
+
+ faces_top = createFaces([vert_idx_top], edgeloop_top, closed=True)
+ faces_outside = createFaces(edgeloop_top, edgeloop_bottom, closed=True)
+ faces_bottom = createFaces([vert_idx_bottom], edgeloop_bottom,
+ flipped=True, closed=True)
+
+ faces.extend(faces_top)
+ faces.extend(faces_outside)
+ faces.extend(faces_bottom)
+
+ return verts, faces
+
+def trapezohedron(s,r,h):
+ """
+ s = segments
+ r = base radius
+ h = tip height
+ """
+
+ # calculate constants
+ a = 2*pi/(2*s) # angle between points along the equator
+ l = r*cos(a) # helper for e
+ e = h*(r-l)/(l+r) # the z offset for each vector along the equator so faces are planar
+
+ # rotation for the points
+ quat = Quaternion((0,0,1),a)
+
+ # first 3 vectors, every next one is calculated from the last, and the z-value is negated
+ verts = [Vector(i) for i in [(0,0,h),(0,0,-h),(r,0,e)]]
+ for i in range(2*s-1):
+ verts.append(verts[-1]*quat) # rotate further "a" radians around the z-axis
+ verts[-1].z *= -1 # negate last z-value to account for the zigzag
+
+ faces = []
+ for i in range(2,2+2*s,2):
+ n = [i+1,i+2,i+3] # vertices in current section
+ for j in range(3): # check whether the numbers dont go over len(verts)
+ if n[j]>=2*s+2: n[j]-=2*s # if so, subtract len(verts)-2
+
+ # add faces of current section
+ faces.append([0,i]+n[:2])
+ faces.append([1,n[2],n[1],n[0]])
+
+ return verts,faces
+
+class AddSqorus(bpy.types.Operator):
+ '''Add a sqorus mesh.'''
+ bl_idname = "mesh.primitive_sqorus_add"
+ bl_label = "Add Sqorus"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ hole_size = FloatProperty(name="Hole Size",
+ description="Size of the Hole",
+ min=0.01,
+ max=1.99,
+ default=2.0 / 3.0)
+ subdivide = BoolProperty(name="Subdivide Outside",
+ description="Enable to subdivide the faces on the outside." \
+ " This results in equally spaced vertices.",
+ default=True)
+ align_matrix = Matrix()
+
+ def execute(self, context):
+ props = self.properties
+
+ # Create mesh geometry
+ verts, faces = add_sqorus(
+ props.hole_size,
+ props.subdivide)
+
+ # Create mesh object (and meshdata)
+ obj = create_mesh_object(context, verts, [], faces, "Sqorus",
+ props.edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddWedge(bpy.types.Operator):
+ '''Add a wedge mesh.'''
+ bl_idname = "mesh.primitive_wedge_add"
+ bl_label = "Add Wedge"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ size_x = FloatProperty(name="Size X",
+ description="Size along the X axis",
+ min=0.01,
+ max=9999.0,
+ default=2.0)
+ size_y = FloatProperty(name="Size Y",
+ description="Size along the Y axis",
+ min=0.01,
+ max=9999.0,
+ default=2.0)
+ size_z = FloatProperty(name="Size Z",
+ description="Size along the Z axis",
+ min=0.01,
+ max=9999.0,
+ default=2.00)
+ align_matrix = Matrix()
+
+ def execute(self, context):
+ props = self.properties
+
+ verts, faces = add_wedge(
+ props.size_x,
+ props.size_y,
+ props.size_z)
+
+ obj = create_mesh_object(context, verts, [], faces, "Wedge",
+ props.edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddSpindle(bpy.types.Operator):
+ '''Add a spindle mesh.'''
+ bl_idname = "mesh.primitive_spindle_add"
+ bl_label = "Add Spindle"
+ bl_description = "Create a spindle mesh."
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ segments = IntProperty(name="Segments",
+ description="Number of segments of the spindle",
+ min=3,
+ max=512,
+ default=32)
+ radius = FloatProperty(name="Radius",
+ description="Radius of the spindle",
+ min=0.01,
+ max=9999.0,
+ default=1.0)
+ height = FloatProperty(name="Height",
+ description="Height of the spindle",
+ min=0.0,
+ max=100.0,
+ default=1.0)
+ cap_height = FloatProperty(name="Cap Height",
+ description="Cap height of the spindle",
+ min=-9999.0,
+ max=9999.0,
+ default=0.5)
+ align_matrix = Matrix()
+
+ def execute(self, context):
+ props = self.properties
+
+ verts, faces = add_spindle(
+ props.segments,
+ props.radius,
+ props.height,
+ props.cap_height)
+
+ obj = create_mesh_object(context, verts, [], faces, "Spindle",
+ props.edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddStar(bpy.types.Operator):
+ '''Add a star mesh.'''
+ bl_idname = "mesh.primitive_star_add"
+ bl_label = "Add Star"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ points = IntProperty(name="Points",
+ description="Number of points for the star",
+ min=2,
+ max=256,
+ default=5)
+ outer_radius = FloatProperty(name="Outer Radius",
+ description="Outer radius of the star",
+ min=0.01,
+ max=9999.0,
+ default=1.0)
+ innter_radius = FloatProperty(name="Inner Radius",
+ description="Inner radius of the star",
+ min=0.01,
+ max=9999.0,
+ default=0.5)
+ height = FloatProperty(name="Height",
+ description="Height of the star",
+ min=0.01,
+ max=9999.0,
+ default=0.5)
+ align_matrix = Matrix()
+
+ def execute(self, context):
+ props = self.properties
+
+ verts, faces = add_star(
+ props.points,
+ props.outer_radius,
+ props.innter_radius,
+ props.height)
+
+ obj = create_mesh_object(context, verts, [], faces, "Star",
+ props.edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddTrapezohedron(bpy.types.Operator):
+ """Add a trapezohedron"""
+ bl_idname = "mesh.primitive_trapezohedron_add"
+ bl_label = "Add trapezohedron"
+ bl_description = "Create one of the regular solids"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ segments = IntProperty(name = "Segments",
+ description = "Number of repeated segments",
+ default = 4, min = 2, max = 256)
+ radius = FloatProperty(name = "Base radius",
+ description = "Radius of the middle",
+ default = 1.0, min = 0.01, max = 100.0)
+ height = FloatProperty(name = "Tip height",
+ description = "Height of the tip",
+ default = 1, min = 0.01, max = 100.0)
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ align_matrix = Matrix()
+ def execute(self,context):
+ props = self.properties
+ # generate mesh
+ verts,faces = trapezohedron(props.segments,
+ props.radius,
+ props.height)
+
+ obj = create_mesh_object(context, verts, [], faces, "Trapazohedron",
+ props.edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+class INFO_MT_mesh_extras_add(bpy.types.Menu):
+ # Define the "Extras" menu
+ bl_idname = "INFO_MT_mesh_extras_add"
+ bl_label = "Extras"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("mesh.primitive_sqorus_add",
+ text="Sqorus")
+ layout.operator("mesh.primitive_wedge_add",
+ text="Wedge")
+ layout.operator("mesh.primitive_spindle_add",
+ text="Spindle")
+ layout.operator("mesh.primitive_star_add",
+ text="Star")
+ layout.operator("mesh.primitive_trapezohedron_add",
+ text="Trapezohedron")
+
+
+# Register all operators and panels
+import space_info
+
+# Define "Gemstones" menu
+menu_func = (lambda self,
+ context: self.layout.menu("INFO_MT_mesh_extras_add", icon="PLUGIN"))
+
+
+def register():
+ # Register the operators/menus.
+ bpy.types.register(AddSqorus)
+ bpy.types.register(AddWedge)
+ bpy.types.register(AddSpindle)
+ bpy.types.register(AddStar)
+ bpy.types.register(AddTrapezohedron)
+ bpy.types.register(INFO_MT_mesh_extras_add)
+
+ # Add "Gemstones" menu to the "Add Mesh" menu
+ space_info.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ # Unregister the operators/menus.
+ bpy.types.unregister(AddSqorus)
+ bpy.types.unregister(AddWedge)
+ bpy.types.unregister(AddSpindle)
+ bpy.types.unregister(AddStar)
+ bpy.types.unregister(AddTrapezohedron)
+ bpy.types.unregister(INFO_MT_mesh_extras_add)
+
+ # Remove "Gemstones" menu from the "Add Mesh" menu.
+ space_info.INFO_MT_mesh_add.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/add_mesh_gears.py b/add_mesh_gears.py
new file mode 100644
index 00000000..92d4e125
--- /dev/null
+++ b/add_mesh_gears.py
@@ -0,0 +1,949 @@
+# add_mesh_gear.py (c) 2009, 2010 Michel J. Anders (varkenvarken)
+#
+# ***** 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 *****
+
+"""
+What was needed to port it from 2.49 -> 2.50 alpha 0?
+
+The basic functions that calculate the geometry (verts and faces) are mostly
+unchanged (add_tooth, add_spoke, add_gear)
+
+Also, the vertex group API is changed a little bit but the concepts
+are the same:
+=========
+vertexgroup = ob.add_vertex_group('NAME_OF_VERTEXGROUP')
+for i in vertexgroup_vertex_indices:
+ ob.add_vertex_to_group(i, vertexgroup, weight, 'ADD')
+=========
+
+Now for some reason the name does not 'stick' and we have to set it this way:
+vertexgroup.name = 'NAME_OF_VERTEXGROUP'
+
+Conversion to 2.50 also meant we could simply do away with our crude user
+interface.
+Just definining the appropriate properties in the AddGear() operator will
+display the properties in the Blender GUI with the added benefit of making
+it interactive: changing a property will redo the AddGear() operator providing
+the user with instant feedback.
+
+Finally we had to convert/throw away some print statements to print functions
+as Blender nows uses Python 3.x
+
+The code to actually implement the AddGear() function is mostly copied from
+add_mesh_torus() (distributed with Blender).
+"""
+
+bl_addon_info = {
+ 'name': 'Add Mesh: Gears',
+ 'author': 'Michel J. Anders (varkenvarken)',
+ 'version': '2.4.1',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Mesh > Gears ',
+ 'description': 'Adds a mesh Gear to the Add Mesh menu',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Add_Mesh/Add_Gear',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21732&group_id=153&atid=469',
+ 'category': 'Add Mesh'}
+
+import bpy
+import mathutils
+from math import *
+from bpy.props import *
+
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = mathutils.TranslationMatrix(context.scene.cursor_location)
+ obj_align = context.user_preferences.edit.object_align
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ rot = context.space_data.region_3d.view_matrix.rotation_part().invert().resize4x4()
+ else:
+ rot = mathutils.Matrix()
+ align_matrix = loc * rot
+ return align_matrix
+
+# Create a new mesh (object) from verts/edges/faces.
+# verts/edges/faces ... List of vertices/edges/faces for the
+# new mesh (as used in from_pydata).
+# name ... Name of the new mesh (& object).
+# edit ... Replace existing mesh data.
+# Note: Using "edit" will destroy/delete existing mesh data.
+def create_mesh_object(context, verts, edges, faces, name, edit, align_matrix):
+ scene = context.scene
+ obj_act = scene.objects.active
+
+ # Can't edit anything, unless we have an active obj.
+ if edit and not obj_act:
+ return None
+
+ # Create new mesh
+ mesh = bpy.data.meshes.new(name)
+
+ # Make a mesh from a list of verts/edges/faces.
+ mesh.from_pydata(verts, edges, faces)
+
+ # Update mesh geometry after adding stuff.
+ mesh.update()
+
+ # Deselect all objects.
+ bpy.ops.object.select_all(action='DESELECT')
+
+ if edit:
+ # Replace geometry of existing object
+
+ # Use the active obj and select it.
+ ob_new = obj_act
+ ob_new.select = True
+
+ if obj_act.mode == 'OBJECT':
+ # Get existing mesh datablock.
+ old_mesh = ob_new.data
+
+ # Set object data to nothing
+ ob_new.data = None
+
+ # Clear users of existing mesh datablock.
+ old_mesh.user_clear()
+
+ # Remove old mesh datablock if no users are left.
+ if (old_mesh.users == 0):
+ bpy.data.meshes.remove(old_mesh)
+
+ # Assign new mesh datablock.
+ ob_new.data = mesh
+
+ else:
+ # Create new object
+ ob_new = bpy.data.objects.new(name, mesh)
+
+ # Link new object to the given scene and select it.
+ scene.objects.link(ob_new)
+ ob_new.select = True
+
+ # Place the object at the 3D cursor location.
+ # apply viewRotaion
+ ob_new.matrix_world = align_matrix
+
+
+ if obj_act and obj_act.mode == 'EDIT':
+ if not edit:
+ # We are in EditMode, switch to ObjectMode.
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Select the active object as well.
+ obj_act.select = True
+
+ # Apply location of new object.
+ scene.update()
+
+ # Join new object into the active.
+ bpy.ops.object.join()
+
+ # Switching back to EditMode.
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ ob_new = obj_act
+
+ else:
+ # We are in ObjectMode.
+ # Make the new object the active one.
+ scene.objects.active = ob_new
+
+ return ob_new
+
+
+# A very simple "bridge" tool.
+# Connects two equally long vertex rows with faces.
+# Returns a list of the new faces (list of lists)
+#
+# vertIdx1 ... First vertex list (list of vertex indices).
+# vertIdx2 ... Second vertex list (list of vertex indices).
+# closed ... Creates a loop (first & last are closed).
+# flipped ... Invert the normal of the face(s).
+#
+# Note: You can set vertIdx1 to a single vertex index to create
+# a fan/star of faces.
+# Note: If both vertex idx list are the same length they have
+# to have at least 2 vertices.
+def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
+ faces = []
+
+ if not vertIdx1 or not vertIdx2:
+ return None
+
+ if len(vertIdx1) < 2 and len(vertIdx2) < 2:
+ return None
+
+ fan = False
+ if (len(vertIdx1) != len(vertIdx2)):
+ if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
+ fan = True
+ else:
+ return None
+
+ total = len(vertIdx2)
+
+ if closed:
+ # Bridge the start with the end.
+ if flipped:
+ face = [
+ vertIdx1[0],
+ vertIdx2[0],
+ vertIdx2[total - 1]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ faces.append(face)
+
+ else:
+ face = [vertIdx2[0], vertIdx1[0]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ face.append(vertIdx2[total - 1])
+ faces.append(face)
+
+ # Bridge the rest of the faces.
+ for num in range(total - 1):
+ if flipped:
+ if fan:
+ face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx2[num], vertIdx1[num],
+ vertIdx1[num + 1], vertIdx2[num + 1]]
+ faces.append(face)
+ else:
+ if fan:
+ face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx1[num], vertIdx2[num],
+ vertIdx2[num + 1], vertIdx1[num + 1]]
+ faces.append(face)
+
+ return faces
+
+
+# Calculate the vertex coordinates for a single
+# section of a gear tooth.
+# Returns 4 lists of vertex coords (list of tuples):
+# *-*---*---* (1.) verts_inner_base
+# | | | |
+# *-*---*---* (2.) verts_outer_base
+# | | |
+# *---*---* (3.) verts_middle_tooth
+# \ | /
+# *-*-* (4.) verts_tip_tooth
+#
+# a
+# t
+# d
+# radius
+# Ad
+# De
+# base
+# p_angle
+# rack
+# crown
+def add_tooth(a, t, d, radius, Ad, De, base, p_angle, rack=0, crown=0.0):
+ A = [a, a + t / 4, a + t / 2, a + 3 * t / 4]
+ C = [cos(i) for i in A]
+ S = [sin(i) for i in A]
+
+ Ra = radius + Ad
+ Rd = radius - De
+ Rb = Rd - base
+
+ # Pressure angle calc
+ O = Ad * tan(p_angle)
+ p_angle = atan(O / Ra)
+
+ if radius < 0:
+ p_angle = -p_angle
+
+ if rack:
+ S = [sin(t / 4) * I for I in range(-2, 3)]
+ Sp = [0, sin(-t / 4 + p_angle), 0, sin(t / 4 - p_angle)]
+
+ verts_inner_base = [(Rb, radius * S[I], d) for I in range(4)]
+ verts_outer_base = [(Rd, radius * S[I], d) for I in range(4)]
+ verts_middle_tooth = [(radius, radius * S[I], d) for I in range(1, 4)]
+ verts_tip_tooth = [(Ra, radius * Sp[I], d) for I in range(1, 4)]
+
+ else:
+ Cp = [
+ 0,
+ cos(a + t / 4 + p_angle),
+ cos(a + t / 2),
+ cos(a + 3 * t / 4 - p_angle)]
+ Sp = [0,
+ sin(a + t / 4 + p_angle),
+ sin(a + t / 2),
+ sin(a + 3 * t / 4 - p_angle)]
+
+ verts_inner_base = [(Rb * C[I], Rb * S[I], d)
+ for I in range(4)]
+ verts_outer_base = [(Rd * C[I], Rd * S[I], d)
+ for I in range(4)]
+ verts_middle_tooth = [(radius * C[I], radius * S[I], d + crown / 3)
+ for I in range(1, 4)]
+ verts_tip_tooth = [(Ra * Cp[I], Ra * Sp[I], d + crown)
+ for I in range(1, 4)]
+
+ return (verts_inner_base, verts_outer_base,
+ verts_middle_tooth, verts_tip_tooth)
+
+
+# EXPERIMENTAL Calculate the vertex coordinates for a single
+# section of a gearspoke.
+# Returns them as a list of tuples.
+#
+# a
+# t
+# d
+# radius
+# De
+# base
+# s
+# w
+# l
+# gap
+# width
+#
+# @todo Finish this.
+def add_spoke(a, t, d, radius, De, base, s, w, l, gap=0, width=19):
+ Rd = radius - De
+ Rb = Rd - base
+ Rl = Rb
+
+ verts = []
+ edgefaces = []
+ edgefaces2 = []
+ sf = []
+
+ if not gap:
+ for N in range(width, 1, -2):
+ edgefaces.append(len(verts))
+ ts = t / 4
+ tm = a + 2 * ts
+ te = asin(w / Rb)
+ td = te - ts
+ t4 = ts + td * (width - N) / (width - 3.0)
+ A = [tm + (i - int(N / 2)) * t4 for i in range(N)]
+ C = [cos(i) for i in A]
+ S = [sin(i) for i in A]
+
+ verts.extend([(Rb * I, Rb * J, d) for (I, J) in zip(C, S)])
+ edgefaces2.append(len(verts) - 1)
+
+ Rb = Rb - s
+
+ n = 0
+ for N in range(width, 3, -2):
+ sf.extend([(i + n, i + 1 + n, i + 2 + n, i + N + n)
+ for i in range(0, N - 1, 2)])
+ sf.extend([(i + 2 + n, i + N + n, i + N + 1 + n, i + N + 2 + n)
+ for i in range(0, N - 3, 2)])
+
+ n = n + N
+
+ return verts, edgefaces, edgefaces2, sf
+
+
+# Create gear geometry.
+# Returns:
+# * A list of vertices (list of tuples)
+# * A list of faces (list of lists)
+# * A list (group) of vertices of the tip (list of vertex indices).
+# * A list (group) of vertices of the valley (list of vertex indices).
+#
+# teethNum ... Number of teeth on the gear.
+# radius ... Radius of the gear, negative for crown gear
+# Ad ... Addendum, extent of tooth above radius.
+# De ... Dedendum, extent of tooth below radius.
+# base ... Base, extent of gear below radius.
+# p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
+# width ... Width, thickness of gear.
+# skew ... Skew of teeth. (radiant)
+# conangle ... Conical angle of gear. (radiant)
+# rack
+# crown ... Inward pointing extend of crown teeth.
+#
+# inner radius = radius - (De + base)
+def add_gear(teethNum, radius, Ad, De, base, p_angle,
+ width=1, skew=0, conangle=0, rack=0, crown=0.0):
+
+ if teethNum < 2:
+ return None, None, None, None
+
+ t = 2 * pi / teethNum
+
+ if rack:
+ teethNum = 1
+
+ scale = (radius - 2 * width * tan(conangle)) / radius
+
+ verts = []
+ faces = []
+ vgroup_top = [] # Vertex group of top/tip? vertices.
+ vgroup_valley = [] # Vertex group of valley vertices
+
+ verts_bridge_prev = []
+ for toothCnt in range(teethNum):
+ a = toothCnt * t
+
+ verts_bridge_start = []
+ verts_bridge_end = []
+
+ verts_outside_top = []
+ verts_outside_bottom = []
+ for (s, d, c, top) \
+ in [(0, -width, 1, True), \
+ (skew, width, scale, False)]:
+
+ verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
+ radius * c, Ad * c, De * c, base * c, p_angle,
+ rack, crown)
+
+ vertsIdx1 = list(range(len(verts), len(verts) + len(verts1)))
+ verts.extend(verts1)
+ vertsIdx2 = list(range(len(verts), len(verts) + len(verts2)))
+ verts.extend(verts2)
+ vertsIdx3 = list(range(len(verts), len(verts) + len(verts3)))
+ verts.extend(verts3)
+ vertsIdx4 = list(range(len(verts), len(verts) + len(verts4)))
+ verts.extend(verts4)
+
+ verts_outside = []
+ verts_outside.extend(vertsIdx2[:2])
+ verts_outside.append(vertsIdx3[0])
+ verts_outside.extend(vertsIdx4)
+ verts_outside.append(vertsIdx3[-1])
+ verts_outside.append(vertsIdx2[-1])
+
+ if top:
+ #verts_inside_top = vertsIdx1
+ verts_outside_top = verts_outside
+
+ verts_bridge_start.append(vertsIdx1[0])
+ verts_bridge_start.append(vertsIdx2[0])
+ verts_bridge_end.append(vertsIdx1[-1])
+ verts_bridge_end.append(vertsIdx2[-1])
+
+ else:
+ #verts_inside_bottom = vertsIdx1
+ verts_outside_bottom = verts_outside
+
+ verts_bridge_start.append(vertsIdx2[0])
+ verts_bridge_start.append(vertsIdx1[0])
+ verts_bridge_end.append(vertsIdx2[-1])
+ verts_bridge_end.append(vertsIdx1[-1])
+
+ # Valley = first 2 vertices of outer base:
+ vgroup_valley.extend(vertsIdx2[:1])
+ # Top/tip vertices:
+ vgroup_top.extend(vertsIdx4)
+
+ faces_tooth_middle_top = createFaces(vertsIdx2[1:], vertsIdx3,
+ flipped=top)
+ faces_tooth_outer_top = createFaces(vertsIdx3, vertsIdx4,
+ flipped=top)
+
+ faces_base_top = createFaces(vertsIdx1, vertsIdx2, flipped=top)
+ faces.extend(faces_base_top)
+
+ faces.extend(faces_tooth_middle_top)
+ faces.extend(faces_tooth_outer_top)
+
+ #faces_inside = createFaces(verts_inside_top, verts_inside_bottom)
+ #faces.extend(faces_inside)
+
+ faces_outside = createFaces(verts_outside_top, verts_outside_bottom,
+ flipped=True)
+ faces.extend(faces_outside)
+
+ if toothCnt == 0:
+ verts_bridge_first = verts_bridge_start
+
+ # Bridge one tooth to the next
+ if verts_bridge_prev:
+ faces_bridge = createFaces(verts_bridge_prev, verts_bridge_start)
+ #, closed=True (for "inside" faces)
+ faces.extend(faces_bridge)
+
+ # Remember "end" vertices for next tooth.
+ verts_bridge_prev = verts_bridge_end
+
+ # Bridge the first to the last tooth.
+ faces_bridge_f_l = createFaces(verts_bridge_prev, verts_bridge_first)
+ #, closed=True (for "inside" faces)
+ faces.extend(faces_bridge_f_l)
+
+ return verts, faces, vgroup_top, vgroup_valley
+
+
+# Create spokes geometry.
+# Returns:
+# * A list of vertices (list of tuples)
+# * A list of faces (list of lists)
+#
+# teethNum ... Number of teeth on the gear.
+# radius ... Radius of the gear, negative for crown gear
+# De ... Dedendum, extent of tooth below radius.
+# base ... Base, extent of gear below radius.
+# width ... Width, thickness of gear.
+# conangle ... Conical angle of gear. (radiant)
+# rack
+# spoke
+# spbevel
+# spwidth
+# splength
+# spresol
+#
+# @todo Finish this
+# @todo Create a function that takes a "Gear" and creates a
+# matching "Gear Spokes" object.
+def add_spokes(teethNum, radius, De, base, width=1, conangle=0, rack=0,
+ spoke=3, spbevel=0.1, spwidth=0.2, splength=1.0, spresol=9):
+
+ if teethNum < 2:
+ return None, None, None, None
+
+ if spoke < 2:
+ return None, None, None, None
+
+ t = 2 * pi / teethNum
+
+ if rack:
+ teethNum = 1
+
+ scale = (radius - 2 * width * tan(conangle)) / radius
+
+ verts = []
+ faces = []
+
+ c = scale # debug
+
+ fl = len(verts)
+ for toothCnt in range(teethNum):
+ a = toothCnt * t
+ s = 0 # For test
+
+ if toothCnt % spoke == 0:
+ for d in (-width, width):
+ sv, edgefaces, edgefaces2, sf = add_spoke(a + s, t, d,
+ radius * c, De * c, base * c,
+ spbevel, spwidth, splength, 0, spresol)
+ verts.extend(sv)
+ faces.extend([[j + fl for j in i] for i in sf])
+ fl += len(sv)
+
+ d1 = fl - len(sv)
+ d2 = fl - 2 * len(sv)
+
+ faces.extend([(i + d2, j + d2, j + d1, i + d1)
+ for (i, j) in zip(edgefaces[:-1], edgefaces[1:])])
+ faces.extend([(i + d2, j + d2, j + d1, i + d1)
+ for (i, j) in zip(edgefaces2[:-1], edgefaces2[1:])])
+
+ else:
+ for d in (-width, width):
+ sv, edgefaces, edgefaces2, sf = add_spoke(a + s, t, d,
+ radius * c, De * c, base * c,
+ spbevel, spwidth, splength, 1, spresol)
+
+ verts.extend(sv)
+ fl += len(sv)
+
+ d1 = fl - len(sv)
+ d2 = fl - 2 * len(sv)
+
+ faces.extend([[i + d2, i + 1 + d2, i + 1 + d1, i + d1]
+ for (i) in range(0, 3)])
+ faces.extend([[i + d2, i + 1 + d2, i + 1 + d1, i + d1]
+ for (i) in range(5, 8)])
+
+ return verts, faces
+
+
+# Create worm geometry.
+# Returns:
+# * A list of vertices
+# * A list of faces
+# * A list (group) of vertices of the tip
+# * A list (group) of vertices of the valley
+#
+# teethNum ... Number of teeth on the worm
+# radius ... Radius of the gear, negative for crown gear
+# Ad ... Addendum, extent of tooth above radius.
+# De ... Dedendum, extent of tooth below radius.
+# p_angle ... Pressure angle. Skewness of tooth tip. (radiant)
+# width ... Width, thickness of gear.
+# crown ... Inward pointing extend of crown teeth.
+#
+# @todo: Fix teethNum. Some numbers are not possible yet.
+# @todo: Create start & end geoemtry (closing faces)
+def add_worm(teethNum, rowNum, radius, Ad, De, p_angle,
+ width=1, skew=radians(11.25), crown=0.0):
+
+ worm = teethNum
+ teethNum = 24
+
+ t = 2 * pi / teethNum
+
+ verts = []
+ faces = []
+ vgroup_top = [] # Vertex group of top/tip? vertices.
+ vgroup_valley = [] # Vertex group of valley vertices
+
+ #width = width / 2.0
+
+ edgeloop_prev = []
+ for Row in range(rowNum):
+ edgeloop = []
+
+ for toothCnt in range(teethNum):
+ a = toothCnt * t
+
+ s = Row * skew
+ d = Row * width
+ c = 1
+
+ isTooth = False
+ if toothCnt % (teethNum / worm) != 0:
+ # Flat
+ verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
+ radius - De, 0.0, 0.0, 0, p_angle)
+
+ # Ignore other verts than the "other base".
+ verts1 = verts3 = verts4 = []
+
+ else:
+ # Tooth
+ isTooth = True
+ verts1, verts2, verts3, verts4 = add_tooth(a + s, t, d,
+ radius * c, Ad * c, De * c, 0 * c, p_angle, 0, crown)
+
+ # Remove various unneeded verts (if we are "inside" the tooth)
+ del(verts2[2]) # Central vertex in the base of the tooth.
+ del(verts3[1]) # Central vertex in the middle of the tooth.
+
+ vertsIdx2 = list(range(len(verts), len(verts) + len(verts2)))
+ verts.extend(verts2)
+ vertsIdx3 = list(range(len(verts), len(verts) + len(verts3)))
+ verts.extend(verts3)
+ vertsIdx4 = list(range(len(verts), len(verts) + len(verts4)))
+ verts.extend(verts4)
+
+ if isTooth:
+ verts_current = []
+ verts_current.extend(vertsIdx2[:2])
+ verts_current.append(vertsIdx3[0])
+ verts_current.extend(vertsIdx4)
+ verts_current.append(vertsIdx3[-1])
+ verts_current.append(vertsIdx2[-1])
+
+ # Valley = first 2 vertices of outer base:
+ vgroup_valley.extend(vertsIdx2[:1])
+ # Top/tip vertices:
+ vgroup_top.extend(vertsIdx4)
+
+ else:
+ # Flat
+ verts_current = vertsIdx2
+
+ # Valley - all of them.
+ vgroup_valley.extend(vertsIdx2)
+
+ edgeloop.extend(verts_current)
+
+ # Create faces between rings/rows.
+ if edgeloop_prev:
+ faces_row = createFaces(edgeloop, edgeloop_prev, closed=True)
+ faces.extend(faces_row)
+
+ # Remember last ring/row of vertices for next ring/row iteration.
+ edgeloop_prev = edgeloop
+
+ return verts, faces, vgroup_top, vgroup_valley
+
+
+class AddGear(bpy.types.Operator):
+ '''Add a gear mesh.'''
+ bl_idname = "mesh.primitive_gear"
+ bl_label = "Add Gear"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ number_of_teeth = IntProperty(name="Number of Teeth",
+ description="Number of teeth on the gear",
+ min=2,
+ max=265,
+ default=12)
+ radius = FloatProperty(name="Radius",
+ description="Radius of the gear, negative for crown gear",
+ min=-100.0,
+ max=100.0,
+ default=1.0)
+ addendum = FloatProperty(name="Addendum",
+ description="Addendum, extent of tooth above radius",
+ min=0.01,
+ max=100.0,
+ default=0.1)
+ dedendum = FloatProperty(name="Dedendum",
+ description="Dedendum, extent of tooth below radius",
+ min=0.0,
+ max=100.0,
+ default=0.1)
+ angle = FloatProperty(name="Pressure Angle",
+ description="Pressure angle, skewness of tooth tip (degrees)",
+ min=0.0,
+ max=45.0,
+ default=20.0)
+ base = FloatProperty(name="Base",
+ description="Base, extent of gear below radius",
+ min=0.0,
+ max=100.0,
+ default=0.2)
+ width = FloatProperty(name="Width",
+ description="Width, thickness of gear",
+ min=0.05,
+ max=100.0,
+ default=0.2)
+ skew = FloatProperty(name="Skewness",
+ description="Skew of teeth (degrees)",
+ min=-90.0,
+ max=90.0,
+ default=0.0)
+ conangle = FloatProperty(name="Conical angle",
+ description="Conical angle of gear (degrees)",
+ min=0.0,
+ max=90.0,
+ default=0.0)
+ crown = FloatProperty(name="Crown",
+ description="Inward pointing extend of crown teeth",
+ min=0.0,
+ max=100.0,
+ default=0.0)
+ align_matrix = mathutils.Matrix()
+
+ def draw(self, context):
+ props = self.properties
+ layout = self.layout
+ box = layout.box()
+ box.prop(props, 'number_of_teeth')
+ box = layout.box()
+ box.prop(props, 'radius')
+ box.prop(props, 'width')
+ box.prop(props, 'base')
+ box = layout.box()
+ box.prop(props, 'dedendum')
+ box.prop(props, 'addendum')
+ box = layout.box()
+ box.prop(props, 'angle')
+ box.prop(props, 'skew')
+ box.prop(props, 'conangle')
+ box.prop(props, 'crown')
+
+
+ def execute(self, context):
+ props = self.properties
+
+ verts, faces, verts_tip, verts_valley = add_gear(
+ props.number_of_teeth,
+ props.radius,
+ props.addendum,
+ props.dedendum,
+ props.base,
+ radians(props.angle),
+ width=props.width,
+ skew=radians(props.skew),
+ conangle=radians(props.conangle),
+ crown=props.crown)
+
+ # Actually create the mesh object from this geometry data.
+ obj = create_mesh_object(context, verts, [], faces, "Gear", props.edit, self.align_matrix)
+
+ # Create vertex groups from stored vertices.
+ tipGroup = obj.add_vertex_group('Tips')
+ for vert in verts_tip:
+ obj.add_vertex_to_group(vert, tipGroup, 1.0, 'ADD')
+
+ valleyGroup = obj.add_vertex_group('Valleys')
+ for vert in verts_valley:
+ obj.add_vertex_to_group(vert, valleyGroup, 1.0, 'ADD')
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddWormGear(bpy.types.Operator):
+ '''Add a worm gear mesh.'''
+ bl_idname = "mesh.primitive_worm_gear"
+ bl_label = "Add Worm Gear"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ number_of_teeth = IntProperty(name="Number of Teeth",
+ description="Number of teeth on the gear",
+ min=2,
+ max=265,
+ default=12)
+ number_of_rows = IntProperty(name="Number of Rows",
+ description="Number of rows on the worm gear",
+ min=2,
+ max=265,
+ default=32)
+ radius = FloatProperty(name="Radius",
+ description="Radius of the gear, negative for crown gear",
+ min=-100.0,
+ max=100.0,
+ default=1.0)
+ addendum = FloatProperty(name="Addendum",
+ description="Addendum, extent of tooth above radius",
+ min=0.01,
+ max=100.0,
+ default=0.1)
+ dedendum = FloatProperty(name="Dedendum",
+ description="Dedendum, extent of tooth below radius",
+ min=0.0,
+ max=100.0,
+ default=0.1)
+ angle = FloatProperty(name="Pressure Angle",
+ description="Pressure angle, skewness of tooth tip (degrees)",
+ min=0.0,
+ max=45.0,
+ default=20.0)
+ row_height = FloatProperty(name="Row Height",
+ description="Height of each Row",
+ min=0.05,
+ max=100.0,
+ default=0.2)
+ skew = FloatProperty(name="Skewness per Row",
+ description="Skew of each row (degrees)",
+ min=-90.0,
+ max=90.0,
+ default=11.25)
+ crown = FloatProperty(name="Crown",
+ description="Inward pointing extend of crown teeth",
+ min=0.0,
+ max=100.0,
+ default=0.0)
+ align_matrix = mathutils.Matrix()
+
+ def draw(self, context):
+ props = self.properties
+ layout = self.layout
+ box = layout.box()
+ box.prop(props, 'number_of_teeth')
+ box.prop(props, 'number_of_rows')
+ box.prop(props, 'radius')
+ box.prop(props, 'row_height')
+ box = layout.box()
+ box.prop(props, 'addendum')
+ box.prop(props, 'dedendum')
+ box = layout.box()
+ box.prop(props, 'angle')
+ box.prop(props, 'skew')
+ box.prop(props, 'crown')
+
+ def execute(self, context):
+ props = self.properties
+
+ verts, faces, verts_tip, verts_valley = add_worm(
+ props.number_of_teeth,
+ props.number_of_rows,
+ props.radius,
+ props.addendum,
+ props.dedendum,
+ radians(props.angle),
+ width=props.row_height,
+ skew=radians(props.skew),
+ crown=props.crown)
+
+ # Actually create the mesh object from this geometry data.
+ obj = create_mesh_object(context, verts, [], faces, "Worm Gear",
+ props.edit, self.align_matrix)
+
+ # Create vertex groups from stored vertices.
+ tipGroup = obj.add_vertex_group('Tips')
+ for vert in verts_tip:
+ obj.add_vertex_to_group(vert, tipGroup, 1.0, 'ADD')
+
+ valleyGroup = obj.add_vertex_group('Valleys')
+ for vert in verts_valley:
+ obj.add_vertex_to_group(vert, valleyGroup, 1.0, 'ADD')
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class INFO_MT_mesh_gears_add(bpy.types.Menu):
+ # Define the "Gears" menu
+ bl_idname = "INFO_MT_mesh_gears_add"
+ bl_label = "Gears"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("mesh.primitive_gear",
+ text="Gear")
+ layout.operator("mesh.primitive_worm_gear",
+ text="Worm")
+
+
+# Define "Gears" menu
+menu_func = (lambda self,
+ context: self.layout.menu("INFO_MT_mesh_gears_add", icon="PLUGIN"))
+
+
+def register():
+ bpy.types.register(AddGear)
+ bpy.types.register(AddWormGear)
+ bpy.types.register(INFO_MT_mesh_gears_add)
+
+ # Add "Gears" entry to the "Add Mesh" menu.
+ bpy.types.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ bpy.types.unregister(AddGear)
+ bpy.types.unregister(AddWormGear)
+ bpy.types.unregister(INFO_MT_mesh_gears_add)
+
+ # Remove "Gears" entry from the "Add Mesh" menu.
+ bpy.types.INFO_MT_mesh_add.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/add_mesh_gemstones.py b/add_mesh_gemstones.py
new file mode 100644
index 00000000..0b27829e
--- /dev/null
+++ b/add_mesh_gemstones.py
@@ -0,0 +1,480 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+from mathutils import *
+from math import *
+from bpy.props import *
+
+bl_addon_info = {
+ 'name': 'Add Mesh: Gemstones',
+ 'author': 'Pontiac, Fourmadmen, Dreampainter',
+ 'version': '0.3',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Mesh > Gemstones',
+ 'description': 'Adds various gemstone (Diamond & Gem) meshes.',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Add_Mesh/Add_Gemstones',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21432&group_id=153&atid=469',
+ 'category': 'Add Mesh'}
+
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = TranslationMatrix(context.scene.cursor_location)
+ obj_align = context.user_preferences.edit.object_align
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ rot = context.space_data.region_3d.view_matrix.rotation_part().invert().resize4x4()
+ else:
+ rot = Matrix()
+ align_matrix = loc * rot
+ return align_matrix
+
+# Create a new mesh (object) from verts/edges/faces.
+# verts/edges/faces ... List of vertices/edges/faces for the
+# new mesh (as used in from_pydata).
+# name ... Name of the new mesh (& object).
+# edit ... Replace existing mesh data.
+# Note: Using "edit" will destroy/delete existing mesh data.
+def create_mesh_object(context, verts, edges, faces, name, edit, align_matrix):
+ scene = context.scene
+ obj_act = scene.objects.active
+
+ # Can't edit anything, unless we have an active obj.
+ if edit and not obj_act:
+ return None
+
+ # Create new mesh
+ mesh = bpy.data.meshes.new(name)
+
+ # Make a mesh from a list of verts/edges/faces.
+ mesh.from_pydata(verts, edges, faces)
+
+ # Update mesh geometry after adding stuff.
+ mesh.update()
+
+ # Deselect all objects.
+ bpy.ops.object.select_all(action='DESELECT')
+
+ if edit:
+ # Replace geometry of existing object
+
+ # Use the active obj and select it.
+ ob_new = obj_act
+ ob_new.select = True
+
+ if obj_act.mode == 'OBJECT':
+ # Get existing mesh datablock.
+ old_mesh = ob_new.data
+
+ # Set object data to nothing
+ ob_new.data = None
+
+ # Clear users of existing mesh datablock.
+ old_mesh.user_clear()
+
+ # Remove old mesh datablock if no users are left.
+ if (old_mesh.users == 0):
+ bpy.data.meshes.remove(old_mesh)
+
+ # Assign new mesh datablock.
+ ob_new.data = mesh
+
+ else:
+ # Create new object
+ ob_new = bpy.data.objects.new(name, mesh)
+
+ # Link new object to the given scene and select it.
+ scene.objects.link(ob_new)
+ ob_new.select = True
+
+ # Place the object at the 3D cursor location.
+ # apply viewRotaion
+ ob_new.matrix_world = align_matrix
+
+ if obj_act and obj_act.mode == 'EDIT':
+ if not edit:
+ # We are in EditMode, switch to ObjectMode.
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Select the active object as well.
+ obj_act.select = True
+
+ # Apply location of new object.
+ scene.update()
+
+ # Join new object into the active.
+ bpy.ops.object.join()
+
+ # Switching back to EditMode.
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ ob_new = obj_act
+
+ else:
+ # We are in ObjectMode.
+ # Make the new object the active one.
+ scene.objects.active = ob_new
+
+ return ob_new
+
+
+# A very simple "bridge" tool.
+# Connects two equally long vertex rows with faces.
+# Returns a list of the new faces (list of lists)
+#
+# vertIdx1 ... First vertex list (list of vertex indices).
+# vertIdx2 ... Second vertex list (list of vertex indices).
+# closed ... Creates a loop (first & last are closed).
+# flipped ... Invert the normal of the face(s).
+#
+# Note: You can set vertIdx1 to a single vertex index to create
+# a fan/star of faces.
+# Note: If both vertex idx list are the same length they have
+# to have at least 2 vertices.
+def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
+ faces = []
+
+ if not vertIdx1 or not vertIdx2:
+ return None
+
+ if len(vertIdx1) < 2 and len(vertIdx2) < 2:
+ return None
+
+ fan = False
+ if (len(vertIdx1) != len(vertIdx2)):
+ if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
+ fan = True
+ else:
+ return None
+
+ total = len(vertIdx2)
+
+ if closed:
+ # Bridge the start with the end.
+ if flipped:
+ face = [
+ vertIdx1[0],
+ vertIdx2[0],
+ vertIdx2[total - 1]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ faces.append(face)
+
+ else:
+ face = [vertIdx2[0], vertIdx1[0]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ face.append(vertIdx2[total - 1])
+ faces.append(face)
+
+ # Bridge the rest of the faces.
+ for num in range(total - 1):
+ if flipped:
+ if fan:
+ face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx2[num], vertIdx1[num],
+ vertIdx1[num + 1], vertIdx2[num + 1]]
+ faces.append(face)
+ else:
+ if fan:
+ face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx1[num], vertIdx2[num],
+ vertIdx2[num + 1], vertIdx1[num + 1]]
+ faces.append(face)
+
+ return faces
+
+
+# @todo Clean up vertex&face creation process a bit.
+def add_gem(r1, r2, seg, h1, h2):
+ """
+ r1 = pavilion radius
+ r2 = crown radius
+ seg = number of segments
+ h1 = pavilion height
+ h2 = crown height
+ Generates the vertices and faces of the gem
+ """
+
+ verts = []
+
+ a = 2.0 * pi / seg # Angle between segments
+ offset = a / 2.0 # Middle between segments
+
+ r3 = ((r1 + r2) / 2.0) / cos(offset) # Middle of crown
+ r4 = (r1 / 2.0) / cos(offset) # Middle of pavilion
+ h3 = h2 / 2.0 # Middle of crown height
+ h4 = -h1 / 2.0 # Middle of pavilion height
+
+ # Tip
+ vert_tip = len(verts)
+ verts.append(Vector((0.0, 0.0, -h1)))
+
+ # Middle vertex of the flat side (crown)
+ vert_flat = len(verts)
+ verts.append(Vector((0.0, 0.0, h2)))
+
+ edgeloop_flat = []
+ for i in range(seg):
+ s1 = sin(i * a)
+ s2 = sin(offset + i * a)
+ c1 = cos(i * a)
+ c2 = cos(offset + i * a)
+
+ verts.append((r4 * s1, r4 * c1, h4)) # Middle of pavilion
+ verts.append((r1 * s2, r1 * c2, 0.0)) # Pavilion
+ verts.append((r3 * s1, r3 * c1, h3)) # Middle crown
+ edgeloop_flat.append(len(verts))
+ verts.append((r2 * s2, r2 * c2, h2)) # Crown
+
+ faces = []
+
+ for index in range(seg):
+ i = index * 4
+ j = ((index + 1) % seg) * 4
+
+ faces.append([j + 2, vert_tip, i + 2, i + 3]) # Tip -> Middle of pav
+ faces.append([j + 2, i + 3, j + 3]) # Middle of pav -> pav
+ faces.append([j + 3, i + 3, j + 4]) # Pav -> Middle crown
+ faces.append([j + 4, i + 3, i + 4, i + 5]) # Crown quads
+ faces.append([j + 4, i + 5, j + 5]) # Middle crown -> crown
+
+ faces_flat = createFaces([vert_flat], edgeloop_flat, closed=True)
+ faces.extend(faces_flat)
+
+ return verts, faces
+
+
+def add_diamond(segments, girdle_radius, table_radius,
+ crown_height, pavilion_height):
+
+ PI_2 = pi * 2.0
+ z_axis = (0.0, 0.0, -1.0)
+
+ verts = []
+ faces = []
+
+ height_flat = crown_height
+ height_middle = 0.0
+ height_tip = -pavilion_height
+
+ # Middle vertex of the flat side (crown)
+ vert_flat = len(verts)
+ verts.append(Vector((0.0, 0.0, height_flat)))
+
+ # Tip
+ vert_tip = len(verts)
+ verts.append(Vector((0.0, 0.0, height_tip)))
+
+ verts_flat = []
+ verts_girdle = []
+
+ for index in range(segments):
+ quat = Quaternion(z_axis, (index / segments) * PI_2)
+
+ angle = PI_2 * index / segments
+
+ # Row for flat side
+ verts_flat.append(len(verts))
+ vec = Vector((table_radius, 0.0, height_flat)) * quat
+ verts.append(vec)
+
+ # Row for the middle/girdle
+ verts_girdle.append(len(verts))
+ vec = Vector((girdle_radius, 0.0, height_middle)) * quat
+ verts.append(vec)
+
+ # Flat face
+ faces_flat = createFaces([vert_flat], verts_flat, closed=True,
+ flipped=True)
+ # Side face
+ faces_side = createFaces(verts_girdle, verts_flat, closed=True)
+ # Tip faces
+ faces_tip = createFaces([vert_tip], verts_girdle, closed=True)
+
+ faces.extend(faces_tip)
+ faces.extend(faces_side)
+ faces.extend(faces_flat)
+
+ return verts, faces
+
+
+class AddDiamond(bpy.types.Operator):
+ '''Add a diamond mesh.'''
+ bl_idname = "mesh.primitive_diamond_add"
+ bl_label = "Add Diamond"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ segments = IntProperty(name="Segments",
+ description="Number of segments for the diamond",
+ min=3,
+ max=256,
+ default=32)
+ girdle_radius = FloatProperty(name="Girdle Radius",
+ description="Girdle radius of the diamond",
+ min=0.01,
+ max=9999.0,
+ default=1.0)
+ table_radius = FloatProperty(name="Table Radius",
+ description="Girdle radius of the diamond",
+ min=0.01,
+ max=9999.0,
+ default=0.6)
+ crown_height = FloatProperty(name="Crown Height",
+ description="Crown height of the diamond",
+ min=0.01,
+ max=9999.0,
+ default=0.35)
+ pavilion_height = FloatProperty(name="Pavilion Height",
+ description="Pavilion height of the diamond",
+ min=0.01,
+ max=9999.0,
+ default=0.8)
+ align_matrix = Matrix()
+
+ def execute(self, context):
+ props = self.properties
+ verts, faces = add_diamond(props.segments,
+ props.girdle_radius,
+ props.table_radius,
+ props.crown_height,
+ props.pavilion_height)
+
+ obj = create_mesh_object(context, verts, [], faces,
+ "Diamond", props.edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddGem(bpy.types.Operator):
+ """Add a diamond gem"""
+ bl_idname = "mesh.primitive_gem_add"
+ bl_label = "Add Gem"
+ bl_description = "Create an offset faceted gem."
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ segments = IntProperty(name="Segments",
+ description="Longitudial segmentation",
+ min=3,
+ max=265,
+ default=8,)
+ pavilion_radius = FloatProperty(name="Radius",
+ description="Radius of the gem",
+ min=0.01,
+ max=9999.0,
+ default=1.0)
+ crown_radius = FloatProperty(name="Table Radius",
+ description="Radius of the table(top).",
+ min=0.01,
+ max=9999.0,
+ default=0.6)
+ crown_height = FloatProperty(name="Table height",
+ description="Height of the top half.",
+ min=0.01,
+ max=9999.0,
+ default=0.35)
+ pavilion_height = FloatProperty(name="Pavilion height",
+ description="Height of bottom half.",
+ min=0.01,
+ max=9999.0,
+ default=0.8)
+ align_matrix = Matrix()
+
+ def execute(self, context):
+ props = self.properties
+
+ # create mesh
+ verts, faces = add_gem(
+ props.pavilion_radius,
+ props.crown_radius,
+ props.segments,
+ props.pavilion_height,
+ props.crown_height)
+
+ obj = create_mesh_object(context, verts, [], faces, "Gem", props.edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class INFO_MT_mesh_gemstones_add(bpy.types.Menu):
+ # Define the "Gemstones" menu
+ bl_idname = "INFO_MT_mesh_gemstones_add"
+ bl_label = "Gemstones"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("mesh.primitive_diamond_add",
+ text="Diamond")
+ layout.operator("mesh.primitive_gem_add",
+ text="Gem")
+
+
+# Register all operators and panels
+import space_info
+
+# Define "Gemstones" menu
+menu_func = (lambda self,
+ context: self.layout.menu("INFO_MT_mesh_gemstones_add", icon="PLUGIN"))
+
+
+def register():
+ # Register the operators/menus.
+ bpy.types.register(AddDiamond)
+ bpy.types.register(AddGem)
+ bpy.types.register(INFO_MT_mesh_gemstones_add)
+
+ # Add "Gemstones" menu to the "Add Mesh" menu
+ space_info.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ # Unregister the operators/menus.
+ bpy.types.unregister(AddDiamond)
+ bpy.types.unregister(AddGem)
+ bpy.types.unregister(INFO_MT_mesh_gemstones_add)
+
+ # Remove "Gemstones" menu from the "Add Mesh" menu.
+ space_info.INFO_MT_mesh_add.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/add_mesh_pipe_joint.py b/add_mesh_pipe_joint.py
new file mode 100644
index 00000000..97f7d2e5
--- /dev/null
+++ b/add_mesh_pipe_joint.py
@@ -0,0 +1,1300 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Pipe Joints
+This script lets the user create various types of pipe joints.
+
+Usage:
+You have to activated the script in the "Add-Ons" tab (user preferences).
+The functionality can then be accessed via the
+"Add Mesh" -> "Pipe Joints" menu.
+Note: Currently only the "Elbow" type supports odd number of vertices.
+
+Version history:
+v0.10.5 - createFaces can now create fan/star like faces.
+v0.10.4 - Updated the function "createFaces" a bit. No functional changes.
+v0.10.3 - Updated store_recall_properties, apply_object_align
+ and create_mesh_object.
+ Changed how recall data is stored.
+ Added 'description'.
+v0.10.2 - API change Mathutils -> mathutils (r557)
+ Fixed wiki url.
+v0.10.1 - Use hidden "edit" property for "recall" operator.
+v0.10 - Store "recall" properties in the created objects.
+ Align the geometry to the view if the user preference says so.
+v0.9.10 - Use bl_addon_info for Add-On information.
+v0.9.9 - Changed the script so it can be managed from the "Add-Ons" tab in
+ the user preferences.
+ Added dummy "PLUGIN" icon.
+v0.9.8 - Fixed some new API stuff.
+ Mainly we now have the register/unregister functions.
+ Also the new() function for objects now accepts a mesh object.
+ Corrected FSF address.
+ Clean up of tooltips.
+v0.9.7 - Use "unit" settings for angles as well.
+ This also lets me use radiant for all internal values..
+v0.9.6 - Use "unit" settings (i.e. none/metric/imperial).
+v0.9.5 - Use mesh.from_pydata() for geometry creation.
+ So we can remove unpack_list and unpack_face_list again.
+v0.9.4 - Creating of the pipe now works in mesh edit mode too.
+ Thanks to ideasman42 (Campbell Barton) for his nice work
+ on the torus script code :-).
+v0.9.3 - Changed to a saner vertex/polygon creation process (previously
+ my usage of add_geometry could only do quads)
+ For this I've copied the functions unpack_list and unpack_face_list
+ from import_scene_obj.py.
+ Elbow joint actually supports 3 vertices per circle.
+ Various comments.
+ Script _should_ now be PEP8 compatible.
+v0.9.2 - Converted from tabs to spaces (4 spaces per tab).
+v0.9.1 - Converted add_mesh and add_object to their new counterparts
+ "bpy.data.meshes.new() and "bpy.data.objects.new()"
+v0.9 - Converted to 2.5. Made mostly pep8 compatible (exept for tabs and
+ stuff the check-script didn't catch).
+v0.8.5 - Fixed bug in Elbow joint. Same problem as in 0.8.1
+v0.8.4 - Fixed bug in Y joint. Same problem as in 0.8.1
+v0.8.3 - Fixed bug in N joint. Same problem as in 0.8.1
+v0.8.2 - Fixed bug in X (cross) joint. Same problem as in 0.8.1
+v0.8.1 - Fixed bug in T joint. Angles greater than 90 deg combined with a
+ radius != 1 resulted in bad geometry (the radius was not taken into
+ account when calculating the joint vertices).
+v0.8 - Added N-Joint.
+ Removed all uses of baseJointLocZ. It just clutters the code.
+v0.7 - Added cross joint
+v0.6 - No visible changes. Lots of internal ones though
+ (complete redesign of face creation process).
+ As a bonus the code is a bit easier to read now.
+ Added a nice&simple little "bridge" function
+ (createFaces) for these changes.
+v0.5.1 - Made it possible to create asymmetric Y joints.
+ Renamed the 2 Wye Joints to something more fitting and unique.
+ One is now the Tee joint, the second one remains the Wye joint.
+v0.5 - Added real Y joint.
+v0.4.3 - Added check for odd vertex numbers. They are not (yet) supported.
+v0.4.2 - Added pipe length to the GUI.
+v0.4.1 - Removed the unfinished menu entries for now.
+v0.4 - Tried to clean up the face creation in addTeeJoint
+v0.3 - Code for wye (Y) shape (straight pipe with "branch" for now)
+v0.2 - Restructured to allow different types of pipe (joints).
+v0.1 - Initial revision.
+
+More links:
+http://gitorious.org/blender-scripts/blender-pipe-joint-script
+http://blenderartists.org/forum/showthread.php?t=154394
+
+TODO:
+
+Use a rotation matrix for rotating the circle vertices:
+rotation_matrix = mathutils.RotationMatrix(-math.pi/2, 4, 'x')
+mesh.transform(rotation_matrix)
+"""
+
+bl_addon_info = {
+ 'name': 'Add Mesh: Pipe Joints',
+ 'author': 'Buerbaum Martin (Pontiac)',
+ 'version': '0.10.5',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Mesh > Pipe Joint',
+ 'description': 'Adds 5 pipe Joint types to the Add Mesh menu',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Add_Mesh/Add_Pipe_Joints',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21443&group_id=153&atid=469',
+ 'category': 'Add Mesh'}
+
+import bpy
+import mathutils
+from math import *
+from bpy.props import *
+
+
+# Apply view rotation to objects if "Align To" for
+# new objects was set to "VIEW" in the User Preference.
+# Is now handled in the invoke functions
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = mathutils.TranslationMatrix(context.scene.cursor_location)
+ obj_align = context.user_preferences.edit.object_align
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ rot = context.space_data.region_3d.view_matrix.rotation_part().invert().resize4x4()
+ else:
+ rot = mathutils.Matrix()
+ align_matrix = loc * rot
+ return align_matrix
+# Create a new mesh (object) from verts/edges/faces.
+# verts/edges/faces ... List of vertices/edges/faces for the
+# new mesh (as used in from_pydata).
+# name ... Name of the new mesh (& object).
+# edit ... Replace existing mesh data.
+# Note: Using "edit" will destroy/delete existing mesh data.
+def create_mesh_object(context, verts, edges, faces, name, edit, align_matrix):
+ scene = context.scene
+ obj_act = scene.objects.active
+
+ # Can't edit anything, unless we have an active obj.
+ if edit and not obj_act:
+ return None
+
+ # Create new mesh
+ mesh = bpy.data.meshes.new(name)
+
+ # Make a mesh from a list of verts/edges/faces.
+ mesh.from_pydata(verts, edges, faces)
+
+ # Update mesh geometry after adding stuff.
+ mesh.update()
+
+ # Deselect all objects.
+ bpy.ops.object.select_all(action='DESELECT')
+
+ if edit:
+ # Replace geometry of existing object
+
+ # Use the active obj and select it.
+ ob_new = obj_act
+ ob_new.selected = True
+
+ if obj_act.mode == 'OBJECT':
+ # Get existing mesh datablock.
+ old_mesh = ob_new.data
+
+ # Set object data to nothing
+ ob_new.data = None
+
+ # Clear users of existing mesh datablock.
+ old_mesh.user_clear()
+
+ # Remove old mesh datablock if no users are left.
+ if (old_mesh.users == 0):
+ bpy.data.meshes.remove(old_mesh)
+
+ # Assign new mesh datablock.
+ ob_new.data = mesh
+
+ else:
+ # Create new object
+ ob_new = bpy.data.objects.new(name, mesh)
+
+ # Link new object to the given scene and select it.
+ scene.objects.link(ob_new)
+ ob_new.select = True
+
+ # Place the object at the 3D cursor location.
+ ob_new.matrix_world = align_matrix
+
+ if obj_act and obj_act.mode == 'EDIT':
+ if not edit:
+ # We are in EditMode, switch to ObjectMode.
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Select the active object as well.
+ obj_act.select = True
+
+ # Apply location of new object.
+ scene.update()
+
+ # Join new object into the active.
+ bpy.ops.object.join()
+
+ # Switching back to EditMode.
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ ob_new = obj_act
+
+ else:
+ # We are in ObjectMode.
+ # Make the new object the active one.
+ scene.objects.active = ob_new
+
+ return ob_new
+
+
+# A very simple "bridge" tool.
+# Connects two equally long vertex rows with faces.
+# Returns a list of the new faces (list of lists)
+#
+# vertIdx1 ... First vertex list (list of vertex indices).
+# vertIdx2 ... Second vertex list (list of vertex indices).
+# closed ... Creates a loop (first & last are closed).
+# flipped ... Invert the normal of the face(s).
+#
+# Note: You can set vertIdx1 to a single vertex index to create
+# a fan/star of faces.
+# Note: If both vertex idx list are the same length they have
+# to have at least 2 vertices.
+def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
+ faces = []
+
+ if not vertIdx1 or not vertIdx2:
+ return None
+
+ if len(vertIdx1) < 2 and len(vertIdx2) < 2:
+ return None
+
+ fan = False
+ if (len(vertIdx1) != len(vertIdx2)):
+ if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
+ fan = True
+ else:
+ return None
+
+ total = len(vertIdx2)
+
+ if closed:
+ # Bridge the start with the end.
+ if flipped:
+ face = [
+ vertIdx1[0],
+ vertIdx2[0],
+ vertIdx2[total - 1]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ faces.append(face)
+
+ else:
+ face = [vertIdx2[0], vertIdx1[0]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ face.append(vertIdx2[total - 1])
+ faces.append(face)
+
+ # Bridge the rest of the faces.
+ for num in range(total - 1):
+ if flipped:
+ if fan:
+ face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx2[num], vertIdx1[num],
+ vertIdx1[num + 1], vertIdx2[num + 1]]
+ faces.append(face)
+ else:
+ if fan:
+ face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx1[num], vertIdx2[num],
+ vertIdx2[num + 1], vertIdx1[num + 1]]
+ faces.append(face)
+
+ return faces
+
+
+class AddElbowJoint(bpy.types.Operator):
+ # Create the vertices and polygons for a simple elbow (bent pipe).
+ '''Add an Elbow pipe mesh'''
+ bl_idname = "mesh.primitive_elbow_joint_add"
+ bl_label = "Add Pipe Elbow"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+ radius = FloatProperty(name="Radius",
+ description="The radius of the pipe.",
+ default=1.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ div = IntProperty(name="Divisions",
+ description="Number of vertices (divisions).",
+ default=32, min=3, max=256)
+
+ angle = FloatProperty(name="Angle",
+ description="The angle of the branching pipe (i.e. the 'arm')." \
+ " Measured from the center line of the main pipe.",
+ default=radians(45.0),
+ min=radians(-179.9),
+ max=radians(179.9),
+ unit="ROTATION")
+
+ startLength = FloatProperty(name="Length Start",
+ description="Length of the beginning of the pipe.",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ endLength = FloatProperty(name="End Length",
+ description="Length of the end of the pipe.",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ align_matrix = mathutils.Matrix()
+
+ def execute(self, context):
+ edit = self.properties.edit
+
+ radius = self.properties.radius
+ div = self.properties.div
+
+ angle = self.properties.angle
+
+ startLength = self.properties.startLength
+ endLength = self.properties.endLength
+
+ verts = []
+ faces = []
+
+ loop1 = [] # The starting circle
+ loop2 = [] # The elbow circle
+ loop3 = [] # The end circle
+
+ # Create start circle
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+ locZ = -startLength
+ loop1.append(len(verts))
+ verts.append([locX * radius, locY * radius, locZ])
+
+ # Create deformed joint circle
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+ locZ = locX * tan(angle / 2.0)
+ loop2.append(len(verts))
+ verts.append([locX * radius, locY * radius, locZ * radius])
+
+ # Create end circle
+ baseEndLocX = -endLength * sin(angle)
+ baseEndLocZ = endLength * cos(angle)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ # Create circle
+ locX = sin(curVertAngle) * radius
+ locY = cos(curVertAngle) * radius
+ locZ = 0.0
+
+ # Rotate circle
+ locZ = locX * cos(pi / 2.0 - angle)
+ locX = locX * sin(pi / 2.0 - angle)
+
+ loop3.append(len(verts))
+ # Translate and add circle vertices to the list.
+ verts.append([baseEndLocX + locX, locY, baseEndLocZ + locZ])
+
+ # Create faces
+ faces.extend(createFaces(loop1, loop2, closed=True))
+ faces.extend(createFaces(loop2, loop3, closed=True))
+
+ obj = create_mesh_object(context, verts, [], faces,
+ "Elbow Joint", edit, self.align_matrix)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddTeeJoint(bpy.types.Operator):
+ # Create the vertices and polygons for a simple tee (T) joint.
+ # The base arm of the T can be positioned in an angle if needed though.
+ '''Add a Tee-Joint mesh'''
+ bl_idname = "mesh.primitive_tee_joint_add"
+ bl_label = "Add Pipe Tee-Joint"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+ radius = FloatProperty(name="Radius",
+ description="The radius of the pipe.",
+ default=1.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ div = IntProperty(name="Divisions",
+ description="Number of vertices (divisions).",
+ default=32,
+ min=4,
+ max=256)
+
+ angle = FloatProperty(name="Angle",
+ description="The angle of the branching pipe (i.e. the 'arm')." \
+ " Measured from the center line of the main pipe.",
+ default=radians(90.0),
+ min=radians(0.1),
+ max=radians(179.9),
+ unit="ROTATION")
+
+ startLength = FloatProperty(name="Length Start",
+ description="Length of the beginning of the" \
+ " main pipe (the straight one).",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ endLength = FloatProperty(name="End Length",
+ description="Length of the end of the" \
+ " main pipe (the straight one).",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ branchLength = FloatProperty(name="Arm Length",
+ description="Length of the arm pipe (the bent one).",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ align_matrix = mathutils.Matrix()
+
+ def execute(self, context):
+ edit = self.properties.edit
+
+ radius = self.properties.radius
+ div = self.properties.div
+
+ angle = self.properties.angle
+
+ startLength = self.properties.startLength
+ endLength = self.properties.endLength
+ branchLength = self.properties.branchLength
+
+ if (div % 2):
+ # Odd vertice number not supported (yet).
+ return {'CANCELLED'}
+
+ verts = []
+ faces = []
+
+ # List of vert indices of each cross section
+ loopMainStart = [] # Vert indices for the
+ # beginning of the main pipe.
+ loopJoint1 = [] # Vert indices for joint that is used
+ # to connect the joint & loopMainStart.
+ loopJoint2 = [] # Vert indices for joint that is used
+ # to connect the joint & loopArm.
+ loopJoint3 = [] # Vert index for joint that is used
+ # to connect the joint & loopMainEnd.
+ loopArm = [] # Vert indices for the end of the arm.
+ loopMainEnd = [] # Vert indices for the
+ # end of the main pipe.
+
+ # Create start circle (main pipe)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+ locZ = -startLength
+ loopMainStart.append(len(verts))
+ verts.append([locX * radius, locY * radius, locZ])
+
+ # Create deformed joint circle
+ vertTemp1 = None
+ vertTemp2 = None
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+
+ if vertIdx == 0:
+ vertTemp1 = len(verts)
+ if vertIdx == div / 2:
+ # @todo: This will possibly break if we
+ # ever support odd divisions.
+ vertTemp2 = len(verts)
+
+ loopJoint1.append(len(verts))
+ if (vertIdx < div / 2):
+ # Straight side of main pipe.
+ locZ = 0
+ loopJoint3.append(len(verts))
+ else:
+ # Branching side
+ locZ = locX * tan(angle / 2.0)
+ loopJoint2.append(len(verts))
+
+ verts.append([locX * radius, locY * radius, locZ * radius])
+
+ # Create 2. deformed joint (half-)circle
+ loopTemp = []
+ for vertIdx in range(div):
+ if (vertIdx > div / 2):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = -cos(curVertAngle)
+ locZ = -(radius * locX * tan((pi - angle) / 2.0))
+ loopTemp.append(len(verts))
+ verts.append([locX * radius, locY * radius, locZ])
+
+ loopTemp2 = loopTemp[:]
+
+ # Finalise 2. loop
+ loopTemp.reverse()
+ loopTemp.append(vertTemp1)
+ loopJoint2.reverse()
+ loopJoint2.extend(loopTemp)
+ loopJoint2.reverse()
+
+ # Finalise 3. loop
+ loopTemp2.append(vertTemp2)
+ loopTemp2.reverse()
+ loopJoint3.extend(loopTemp2)
+
+ # Create end circle (branching pipe)
+ baseEndLocX = -branchLength * sin(angle)
+ baseEndLocZ = branchLength * cos(angle)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ # Create circle
+ locX = sin(curVertAngle) * radius
+ locY = cos(curVertAngle) * radius
+ locZ = 0.0
+
+ # Rotate circle
+ locZ = locX * cos(pi / 2.0 - angle)
+ locX = locX * sin(pi / 2.0 - angle)
+
+ loopArm.append(len(verts))
+
+ # Add translated circle.
+ verts.append([baseEndLocX + locX, locY, baseEndLocZ + locZ])
+
+ # Create end circle (main pipe)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+ locZ = endLength
+ loopMainEnd.append(len(verts))
+ verts.append([locX * radius, locY * radius, locZ])
+
+ # Create faces
+ faces.extend(createFaces(loopMainStart, loopJoint1, closed=True))
+ faces.extend(createFaces(loopJoint2, loopArm, closed=True))
+ faces.extend(createFaces(loopJoint3, loopMainEnd, closed=True))
+
+ obj = create_mesh_object(context, verts, [], faces, "Tee Joint", edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddWyeJoint(bpy.types.Operator):
+ '''Add a Wye-Joint mesh'''
+ bl_idname = "mesh.primitive_wye_joint_add"
+ bl_label = "Add Pipe Wye-Joint"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+ radius = FloatProperty(name="Radius",
+ description="The radius of the pipe.",
+ default=1.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ div = IntProperty(name="Divisions",
+ description="Number of vertices (divisions).",
+ default=32,
+ min=4,
+ max=256)
+
+ angle1 = FloatProperty(name="Angle 1",
+ description="The angle of the 1. branching pipe." \
+ " Measured from the center line of the main pipe.",
+ default=radians(45.0),
+ min=radians(-179.9),
+ max=radians(179.9),
+ unit="ROTATION")
+ angle2 = FloatProperty(name="Angle 2",
+ description="The angle of the 2. branching pipe." \
+ " Measured from the center line of the main pipe.",
+ default=radians(45.0),
+ min=radians(-179.9),
+ max=radians(179.9),
+ unit="ROTATION")
+
+ startLength = FloatProperty(name="Length Start",
+ description="Length of the beginning of the" \
+ " main pipe (the straight one).",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ branch1Length = FloatProperty(name="Length Arm 1",
+ description="Length of the 1. arm.",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ branch2Length = FloatProperty(name="Length Arm 2",
+ description="Length of the 2. arm.",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ align_matrix = mathutils.Matrix()
+
+ def execute(self, context):
+ edit = self.properties.edit
+
+ radius = self.properties.radius
+ div = self.properties.div
+
+ angle1 = self.properties.angle1
+ angle2 = self.properties.angle2
+
+ startLength = self.properties.startLength
+ branch1Length = self.properties.branch1Length
+ branch2Length = self.properties.branch2Length
+
+ if (div % 2):
+ # Odd vertice number not supported (yet).
+ return {'CANCELLED'}
+
+ verts = []
+ faces = []
+
+ # List of vert indices of each cross section
+ loopMainStart = [] # Vert indices for
+ # the beginning of the main pipe.
+ loopJoint1 = [] # Vert index for joint that is used
+ # to connect the joint & loopMainStart.
+ loopJoint2 = [] # Vert index for joint that
+ # is used to connect the joint & loopArm1.
+ loopJoint3 = [] # Vert index for joint that is
+ # used to connect the joint & loopArm2.
+ loopArm1 = [] # Vert idxs for end of the 1. arm.
+ loopArm2 = [] # Vert idxs for end of the 2. arm.
+
+ # Create start circle
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+ locZ = -startLength
+ loopMainStart.append(len(verts))
+ verts.append([locX * radius, locY * radius, locZ])
+
+ # Create deformed joint circle
+ vertTemp1 = None
+ vertTemp2 = None
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+
+ if vertIdx == 0:
+ vertTemp2 = len(verts)
+ if vertIdx == div / 2:
+ # @todo: This will possibly break if we
+ # ever support odd divisions.
+ vertTemp1 = len(verts)
+
+ loopJoint1.append(len(verts))
+ if (vertIdx > div / 2):
+ locZ = locX * tan(angle1 / 2.0)
+ loopJoint2.append(len(verts))
+ else:
+ locZ = locX * tan(-angle2 / 2.0)
+ loopJoint3.append(len(verts))
+
+ verts.append([locX * radius, locY * radius, locZ * radius])
+
+ # Create 2. deformed joint (half-)circle
+ loopTemp = []
+ angleJoint = (angle2 - angle1) / 2.0
+ for vertIdx in range(div):
+ if (vertIdx > div / 2):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+
+ locX = (-sin(curVertAngle) * sin(angleJoint)
+ / sin(angle2 - angleJoint))
+ locY = -cos(curVertAngle)
+ locZ = (-(sin(curVertAngle) * cos(angleJoint)
+ / sin(angle2 - angleJoint)))
+
+ loopTemp.append(len(verts))
+ verts.append([locX * radius, locY * radius, locZ * radius])
+
+ loopTemp2 = loopTemp[:]
+
+ # Finalise 2. loop
+ loopTemp.append(vertTemp1)
+ loopTemp.reverse()
+ loopTemp.append(vertTemp2)
+ loopJoint2.reverse()
+ loopJoint2.extend(loopTemp)
+ loopJoint2.reverse()
+
+ # Finalise 3. loop
+ loopTemp2.reverse()
+ loopJoint3.extend(loopTemp2)
+
+ # Create end circle (1. branching pipe)
+ baseEndLocX = -branch1Length * sin(angle1)
+ baseEndLocZ = branch1Length * cos(angle1)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ # Create circle
+ locX = sin(curVertAngle) * radius
+ locY = cos(curVertAngle) * radius
+ locZ = 0.0
+
+ # Rotate circle
+ locZ = locX * cos(pi / 2.0 - angle1)
+ locX = locX * sin(pi / 2.0 - angle1)
+
+ loopArm1.append(len(verts))
+ # Add translated circle.
+ verts.append([baseEndLocX + locX, locY, baseEndLocZ + locZ])
+
+ # Create end circle (2. branching pipe)
+ baseEndLocX = branch2Length * sin(angle2)
+ baseEndLocZ = branch2Length * cos(angle2)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ # Create circle
+ locX = sin(curVertAngle) * radius
+ locY = cos(curVertAngle) * radius
+ locZ = 0.0
+
+ # Rotate circle
+ locZ = locX * cos(pi / 2.0 + angle2)
+ locX = locX * sin(pi / 2.0 + angle2)
+
+ loopArm2.append(len(verts))
+ # Add translated circle
+ verts.append([baseEndLocX + locX, locY, baseEndLocZ + locZ])
+
+ # Create faces
+ faces.extend(createFaces(loopMainStart, loopJoint1, closed=True))
+ faces.extend(createFaces(loopJoint2, loopArm1, closed=True))
+ faces.extend(createFaces(loopJoint3, loopArm2, closed=True))
+
+ obj = create_mesh_object(context, verts, [], faces, "Wye Joint", edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddCrossJoint(bpy.types.Operator):
+ '''Add a Cross-Joint mesh'''
+ # Create the vertices and polygons for a coss (+ or X) pipe joint.
+ bl_idname = "mesh.primitive_cross_joint_add"
+ bl_label = "Add Pipe Cross-Joint"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+ radius = FloatProperty(name="Radius",
+ description="The radius of the pipe.",
+ default=1.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ div = IntProperty(name="Divisions",
+ description="Number of vertices (divisions).",
+ default=32,
+ min=4,
+ max=256)
+
+ angle1 = FloatProperty(name="Angle 1",
+ description="The angle of the 1. arm (from the main axis).",
+ default=radians(90.0),
+ min=radians(-179.9),
+ max=radians(179.9),
+ unit="ROTATION")
+ angle2 = FloatProperty(name="Angle 2",
+ description="The angle of the 2. arm (from the main axis).",
+ default=radians(90.0),
+ min=radians(-179.9),
+ max=radians(179.9),
+ unit="ROTATION")
+ angle3 = FloatProperty(name="Angle 3 (center)",
+ description="The angle of the center arm (from the main axis).",
+ default=radians(0.0),
+ min=radians(-179.9),
+ max=radians(179.9),
+ unit="ROTATION")
+
+ startLength = FloatProperty(name="Length Start",
+ description="Length of the beginning of the " \
+ "main pipe (the straight one).",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ branch1Length = FloatProperty(name="Length Arm 1",
+ description="Length of the 1. arm.",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ branch2Length = FloatProperty(name="Length Arm 2",
+ description="Length of the 2. arm.",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ branch3Length = FloatProperty(name="Length Arm 3 (center)",
+ description="Length of the center arm.",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ align_matrix = mathutils.Matrix()
+
+ def execute(self, context):
+ edit = self.properties.edit
+
+ radius = self.properties.radius
+ div = self.properties.div
+
+ angle1 = self.properties.angle1
+ angle2 = self.properties.angle2
+ angle3 = self.properties.angle3
+
+ startLength = self.properties.startLength
+ branch1Length = self.properties.branch1Length
+ branch2Length = self.properties.branch2Length
+ branch3Length = self.properties.branch3Length
+ if (div % 2):
+ # Odd vertice number not supported (yet).
+ return {'CANCELLED'}
+
+ verts = []
+ faces = []
+
+ # List of vert indices of each cross section
+ loopMainStart = [] # Vert indices for the
+ # beginning of the main pipe.
+ loopJoint1 = [] # Vert index for joint that is used
+ # to connect the joint & loopMainStart.
+ loopJoint2 = [] # Vert index for joint that is used
+ # to connect the joint & loopArm1.
+ loopJoint3 = [] # Vert index for joint that is used
+ # to connect the joint & loopArm2.
+ loopJoint4 = [] # Vert index for joint that is used
+ # to connect the joint & loopArm3.
+ loopArm1 = [] # Vert idxs for the end of the 1. arm.
+ loopArm2 = [] # Vert idxs for the end of the 2. arm.
+ loopArm3 = [] # Vert idxs for the center arm end.
+
+ # Create start circle
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+ locZ = -startLength
+ loopMainStart.append(len(verts))
+ verts.append([locX * radius, locY * radius, locZ])
+
+ # Create 1. deformed joint circle
+ vertTemp1 = None
+ vertTemp2 = None
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+
+ if vertIdx == 0:
+ vertTemp2 = len(verts)
+ if vertIdx == div / 2:
+ # @todo: This will possibly break if we
+ # ever support odd divisions.
+ vertTemp1 = len(verts)
+
+ loopJoint1.append(len(verts))
+ if (vertIdx > div / 2):
+ locZ = locX * tan(angle1 / 2.0)
+ loopJoint2.append(len(verts))
+ else:
+ locZ = locX * tan(-angle2 / 2.0)
+ loopJoint3.append(len(verts))
+
+ verts.append([locX * radius, locY * radius, locZ * radius])
+
+ loopTemp2 = loopJoint2[:]
+
+ # Create 2. deformed joint circle
+ loopTempA = []
+ loopTempB = []
+ angleJoint1 = (angle1 - angle3) / 2.0
+ angleJoint2 = (angle2 + angle3) / 2.0
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+
+ # Skip pole vertices
+ # @todo: This will possibly break if
+ # we ever support odd divisions.
+ if not (vertIdx == 0) and not (vertIdx == div / 2):
+
+ if (vertIdx > div / 2):
+ angleJoint = angleJoint1
+ angle = angle1
+ Z = -1.0
+ loopTempA.append(len(verts))
+
+ else:
+ angleJoint = angleJoint2
+ angle = angle2
+ Z = 1.0
+ loopTempB.append(len(verts))
+
+ locX = (sin(curVertAngle) * sin(angleJoint)
+ / sin(angle - angleJoint))
+ locY = -cos(curVertAngle)
+ locZ = (Z * (sin(curVertAngle) * cos(angleJoint)
+ / sin(angle - angleJoint)))
+
+ verts.append([locX * radius, locY * radius, locZ * radius])
+
+ loopTempA2 = loopTempA[:]
+ loopTempB2 = loopTempB[:]
+ loopTempB3 = loopTempB[:]
+
+ # Finalise 2. loop
+ loopTempA.append(vertTemp1)
+ loopTempA.reverse()
+ loopTempA.append(vertTemp2)
+ loopJoint2.reverse()
+ loopJoint2.extend(loopTempA)
+ loopJoint2.reverse()
+
+ # Finalise 3. loop
+ loopJoint3.extend(loopTempB3)
+
+ # Finalise 4. loop
+ loopTempA2.append(vertTemp1)
+ loopTempA2.reverse()
+ loopTempB2.append(vertTemp2)
+ loopJoint4.extend(reversed(loopTempB2))
+ loopJoint4.extend(loopTempA2)
+
+ # Create end circle (1. branching pipe)
+ baseEndLocX = -branch1Length * sin(angle1)
+ baseEndLocZ = branch1Length * cos(angle1)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ # Create circle
+ locX = sin(curVertAngle) * radius
+ locY = cos(curVertAngle) * radius
+ locZ = 0.0
+
+ # Rotate circle
+ locZ = locX * cos(pi / 2.0 - angle1)
+ locX = locX * sin(pi / 2.0 - angle1)
+
+ loopArm1.append(len(verts))
+ # Add translated circle.
+ verts.append([baseEndLocX + locX, locY, baseEndLocZ + locZ])
+
+ # Create end circle (2. branching pipe)
+ baseEndLocX = branch2Length * sin(angle2)
+ baseEndLocZ = branch2Length * cos(angle2)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ # Create circle
+ locX = sin(curVertAngle) * radius
+ locY = cos(curVertAngle) * radius
+ locZ = 0.0
+
+ # Rotate circle
+ locZ = locX * cos(pi / 2.0 + angle2)
+ locX = locX * sin(pi / 2.0 + angle2)
+
+ loopArm2.append(len(verts))
+ # Add translated circle
+ verts.append([baseEndLocX + locX, locY, baseEndLocZ + locZ])
+
+ # Create end circle (center pipe)
+ baseEndLocX = branch3Length * sin(angle3)
+ baseEndLocZ = branch3Length * cos(angle3)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ # Create circle
+ locX = sin(curVertAngle) * radius
+ locY = cos(curVertAngle) * radius
+ locZ = 0.0
+
+ # Rotate circle
+ locZ = locX * cos(pi / 2.0 + angle3)
+ locX = locX * sin(pi / 2.0 + angle3)
+
+ loopArm3.append(len(verts))
+ # Add translated circle
+ verts.append([baseEndLocX + locX, locY, baseEndLocZ + locZ])
+
+ # Create faces
+ faces.extend(createFaces(loopMainStart, loopJoint1, closed=True))
+ faces.extend(createFaces(loopJoint2, loopArm1, closed=True))
+ faces.extend(createFaces(loopJoint3, loopArm2, closed=True))
+ faces.extend(createFaces(loopJoint4, loopArm3, closed=True))
+
+ obj = create_mesh_object(context, verts, [], faces,
+ "Cross Joint", edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class AddNJoint(bpy.types.Operator):
+ '''Add a N-Joint mesh'''
+ # Create the vertices and polygons for a regular n-joint.
+ bl_idname = "mesh.primitive_n_joint_add"
+ bl_label = "Add Pipe N-Joint"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+ radius = FloatProperty(name="Radius",
+ description="The radius of the pipe.",
+ default=1.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ div = IntProperty(name="Divisions",
+ description="Number of vertices (divisions).",
+ default=32,
+ min=4,
+ max=256)
+ number = IntProperty(name="Arms/Joints",
+ description="Number of joints/arms",
+ default=5,
+ min=2,
+ max=99999)
+ length = FloatProperty(name="Length",
+ description="Length of each joint/arm",
+ default=3.0,
+ min=0.01,
+ max=100.0,
+ unit="LENGTH")
+ align_matrix = mathutils.Matrix()
+
+ def execute(self, context):
+ edit = self.properties.edit
+ radius = self.properties.radius
+ div = self.properties.div
+ number = self.properties.number
+ length = self.properties.length
+
+ if (div % 2):
+ # Odd vertice number not supported (yet).
+ return {'CANCELLED'}
+
+ if (number < 2):
+ return {'CANCELLED'}
+
+ verts = []
+ faces = []
+
+ loopsEndCircles = []
+ loopsJointsTemp = []
+ loopsJoints = []
+
+ vertTemp1 = None
+ vertTemp2 = None
+
+ angleDiv = (2.0 * pi / number)
+
+ # Create vertices for the end circles.
+ for num in range(number):
+ circle = []
+ # Create start circle
+ angle = num * angleDiv
+
+ baseEndLocX = length * sin(angle)
+ baseEndLocZ = length * cos(angle)
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ # Create circle
+ locX = sin(curVertAngle) * radius
+ locY = cos(curVertAngle) * radius
+ locZ = 0.0
+
+ # Rotate circle
+ locZ = locX * cos(pi / 2.0 + angle)
+ locX = locX * sin(pi / 2.0 + angle)
+
+ circle.append(len(verts))
+ # Add translated circle
+ verts.append([baseEndLocX + locX, locY, baseEndLocZ + locZ])
+
+ loopsEndCircles.append(circle)
+
+ # Create vertices for the joint circles.
+ loopJoint = []
+ for vertIdx in range(div):
+ curVertAngle = vertIdx * (2.0 * pi / div)
+ locX = sin(curVertAngle)
+ locY = cos(curVertAngle)
+
+ skipVert = False
+ # Store pole vertices
+ if vertIdx == 0:
+ if (num == 0):
+ vertTemp2 = len(verts)
+ else:
+ skipVert = True
+ elif vertIdx == div / 2:
+ # @todo: This will possibly break if we
+ # ever support odd divisions.
+ if (num == 0):
+ vertTemp1 = len(verts)
+ else:
+ skipVert = True
+
+ if not skipVert:
+ if (vertIdx > div / 2):
+ locZ = -locX * tan((pi - angleDiv) / 2.0)
+ loopJoint.append(len(verts))
+
+ # Rotate the vert
+ cosAng = cos(-angle)
+ sinAng = sin(-angle)
+ LocXnew = locX * cosAng - locZ * sinAng
+ LocZnew = locZ * cosAng + locX * sinAng
+ locZ = LocZnew
+ locX = LocXnew
+
+ verts.append([
+ locX * radius,
+ locY * radius,
+ locZ * radius])
+ else:
+ # These two vertices will only be
+ # added the very first time.
+ if vertIdx == 0 or vertIdx == div / 2:
+ verts.append([locX * radius, locY * radius, locZ])
+
+ loopsJointsTemp.append(loopJoint)
+
+ # Create complete loops (loopsJoints) out of the
+ # double number of half loops in loopsJointsTemp.
+ for halfLoopIdx in range(len(loopsJointsTemp)):
+ if (halfLoopIdx == len(loopsJointsTemp) - 1):
+ idx1 = halfLoopIdx
+ idx2 = 0
+ else:
+ idx1 = halfLoopIdx
+ idx2 = halfLoopIdx + 1
+
+ loopJoint = []
+ loopJoint.append(vertTemp2)
+ loopJoint.extend(reversed(loopsJointsTemp[idx2]))
+ loopJoint.append(vertTemp1)
+ loopJoint.extend(loopsJointsTemp[idx1])
+
+ loopsJoints.append(loopJoint)
+
+ # Create faces from the two
+ # loop arrays (loopsJoints -> loopsEndCircles).
+ for loopIdx in range(len(loopsEndCircles)):
+ faces.extend(
+ createFaces(loopsJoints[loopIdx],
+ loopsEndCircles[loopIdx], closed=True))
+
+ obj = create_mesh_object(context, verts, [], faces, "N Joint", edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+class INFO_MT_mesh_pipe_joints_add(bpy.types.Menu):
+ # Define the "Pipe Joints" menu
+ bl_idname = "INFO_MT_mesh_pipe_joints_add"
+ bl_label = "Pipe Joints"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("mesh.primitive_elbow_joint_add",
+ text="Pipe Elbow")
+ layout.operator("mesh.primitive_tee_joint_add",
+ text="Pipe T-Joint")
+ layout.operator("mesh.primitive_wye_joint_add",
+ text="Pipe Y-Joint")
+ layout.operator("mesh.primitive_cross_joint_add",
+ text="Pipe Cross-Joint")
+ layout.operator("mesh.primitive_n_joint_add",
+ text="Pipe N-Joint")
+
+################################
+
+import space_info
+
+# Define "Pipe Joints" menu
+menu_func = (lambda self,
+ context: self.layout.menu("INFO_MT_mesh_pipe_joints_add", icon="PLUGIN"))
+
+
+def register():
+ # Register the operators/menus.
+ bpy.types.register(AddElbowJoint)
+ bpy.types.register(AddTeeJoint)
+ bpy.types.register(AddWyeJoint)
+ bpy.types.register(AddCrossJoint)
+ bpy.types.register(AddNJoint)
+ bpy.types.register(INFO_MT_mesh_pipe_joints_add)
+
+ # Add "Pipe Joints" menu to the "Add Mesh" menu
+ space_info.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ # Unregister the operators/menus.
+ bpy.types.unregister(AddElbowJoint)
+ bpy.types.unregister(AddTeeJoint)
+ bpy.types.unregister(AddWyeJoint)
+ bpy.types.unregister(AddCrossJoint)
+ bpy.types.unregister(AddNJoint)
+ bpy.types.unregister(INFO_MT_mesh_pipe_joints_add)
+
+ # Remove "Pipe Joints" menu from the "Add Mesh" menu.
+ space_info.INFO_MT_mesh_add.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/add_mesh_solid.py b/add_mesh_solid.py
new file mode 100644
index 00000000..579bb954
--- /dev/null
+++ b/add_mesh_solid.py
@@ -0,0 +1,913 @@
+# ***** 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 *****
+
+
+bl_addon_info = {
+ 'name': 'Add Mesh: Regular Solids',
+ 'author': 'DreamPainter',
+ 'version': '1',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Mesh > Regular Solids',
+ 'description': 'Add a Regular Solid mesh.',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Add_Mesh/Add_Solid',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22405&group_id=153&atid=469',
+ 'category': 'Add Mesh'}
+
+
+import bpy
+from bpy.props import FloatProperty,EnumProperty,BoolProperty
+from math import sqrt
+from mathutils import Vector,Matrix
+#from rawMeshUtils import *
+from functools import reduce
+
+# Apply view rotation to objects if "Align To" for
+# new objects was set to "VIEW" in the User Preference.
+def apply_object_align(context, ob):
+ obj_align = bpy.context.user_preferences.edit.object_align
+
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ view3d = context.space_data
+ region = view3d.region_3d
+ viewMatrix = region.view_matrix
+ rot = viewMatrix.rotation_part()
+ ob.rotation_euler = rot.invert().to_euler()
+
+
+# Create a new mesh (object) from verts/edges/faces.
+# verts/edges/faces ... List of vertices/edges/faces for the
+# new mesh (as used in from_pydata).
+# name ... Name of the new mesh (& object).
+# edit ... Replace existing mesh data.
+# Note: Using "edit" will destroy/delete existing mesh data.
+def create_mesh_object(context, verts, edges, faces, name, edit):
+ scene = context.scene
+ obj_act = scene.objects.active
+
+ # Can't edit anything, unless we have an active obj.
+ if edit and not obj_act:
+ return None
+
+ # Create new mesh
+ mesh = bpy.data.meshes.new(name)
+
+ # Make a mesh from a list of verts/edges/faces.
+ mesh.from_pydata(verts, edges, faces)
+
+ # Update mesh geometry after adding stuff.
+ mesh.update()
+
+ # Deselect all objects.
+ bpy.ops.object.select_all(action='DESELECT')
+
+ if edit:
+ # Replace geometry of existing object
+
+ # Use the active obj and select it.
+ ob_new = obj_act
+ ob_new.select = True
+
+ if obj_act.mode == 'OBJECT':
+ # Get existing mesh datablock.
+ old_mesh = ob_new.data
+
+ # Set object data to nothing
+ ob_new.data = None
+
+ # Clear users of existing mesh datablock.
+ old_mesh.user_clear()
+
+ # Remove old mesh datablock if no users are left.
+ if (old_mesh.users == 0):
+ bpy.data.meshes.remove(old_mesh)
+
+ # Assign new mesh datablock.
+ ob_new.data = mesh
+
+ else:
+ # Create new object
+ ob_new = bpy.data.objects.new(name, mesh)
+
+ # Link new object to the given scene and select it.
+ scene.objects.link(ob_new)
+ ob_new.select = True
+
+ # Place the object at the 3D cursor location.
+ ob_new.location = scene.cursor_location
+
+ apply_object_align(context, ob_new)
+
+ if obj_act and obj_act.mode == 'EDIT':
+ if not edit:
+ # We are in EditMode, switch to ObjectMode.
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Select the active object as well.
+ obj_act.select = True
+
+ # Apply location of new object.
+ scene.update()
+
+ # Join new object into the active.
+ bpy.ops.object.join()
+
+ # Switching back to EditMode.
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ ob_new = obj_act
+
+ else:
+ # We are in ObjectMode.
+ # Make the new object the active one.
+ scene.objects.active = ob_new
+
+ return ob_new
+
+
+# A very simple "bridge" tool.
+# Connects two equally long vertex rows with faces.
+# Returns a list of the new faces (list of lists)
+#
+# vertIdx1 ... First vertex list (list of vertex indices).
+# vertIdx2 ... Second vertex list (list of vertex indices).
+# closed ... Creates a loop (first & last are closed).
+# flipped ... Invert the normal of the face(s).
+#
+# Note: You can set vertIdx1 to a single vertex index to create
+# a fan/star of faces.
+# Note: If both vertex idx list are the same length they have
+# to have at least 2 vertices.
+def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
+ faces = []
+
+ if not vertIdx1 or not vertIdx2:
+ return None
+
+ if len(vertIdx1) < 2 and len(vertIdx2) < 2:
+ return None
+
+ fan = False
+ if (len(vertIdx1) != len(vertIdx2)):
+ if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
+ fan = True
+ else:
+ return None
+
+ total = len(vertIdx2)
+
+ if closed:
+ # Bridge the start with the end.
+ if flipped:
+ face = [
+ vertIdx1[0],
+ vertIdx2[0],
+ vertIdx2[total - 1]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ faces.append(face)
+
+ else:
+ face = [vertIdx2[0], vertIdx1[0]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ face.append(vertIdx2[total - 1])
+ faces.append(face)
+
+ # Bridge the rest of the faces.
+ for num in range(total - 1):
+ if flipped:
+ if fan:
+ face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx2[num], vertIdx1[num],
+ vertIdx1[num + 1], vertIdx2[num + 1]]
+ faces.append(face)
+ else:
+ if fan:
+ face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx1[num], vertIdx2[num],
+ vertIdx2[num + 1], vertIdx1[num + 1]]
+ faces.append(face)
+
+ return faces
+# this function creates a chain of quads and, when necessary, a remaining tri
+# for each polygon created in this script. be aware though, that this function
+# assumes each polygon is convex.
+# poly: list of faces, or a single face, like those
+# needed for mesh.from_pydata.
+# returns the tesselated faces.
+def createPolys(poly):
+ # check for faces
+ if len(poly) == 0:
+ return []
+ # one or more faces
+ if type(poly[0]) == type(1):
+ poly = [poly] # if only one, make it a list of one face
+ faces = []
+ for i in poly:
+ l = len(i)
+ # let all faces of 3 or 4 verts be
+ if l < 5:
+ faces.append(i)
+ # split all polygons in half and bridge the two halves
+ else:
+ half = int(l/2)
+ f = createFaces(i[:half],[i[-1-j] for j in range(half)])
+ faces.extend(f)
+ # if the polygon has an odd number of verts, add the last tri
+ if l%2 == 1:
+ faces.append([i[half-1],i[half],i[half+1]])
+ return faces
+
+# function to make the reduce function work as a workaround to sum a list of vectors
+def Asum(list):
+ return reduce(lambda a,b: a+b, list)
+
+# creates the 5 platonic solids as a base for the rest
+# plato: should be one of {"4","6","8","12","20"}. decides what solid the
+# outcome will be.
+# returns a list of vertices and faces and the appropriate name
+def source(plato):
+ verts = []
+ faces = []
+
+ # Tetrahedron
+ if plato == "4":
+ # Calculate the necessary constants
+ s = sqrt(2)/3.0
+ t = -1/3
+ u = sqrt(6)/3
+
+ # create the vertices and faces
+ v = [(0,0,1),(2*s,0,t),(-s,u,t),(-s,-u,t)]
+ faces = [[0,1,2],[0,2,3],[0,3,1],[1,3,2]]
+
+ # Hexahedron (cube)
+ elif plato == "6":
+ # Calculate the necessary constants
+ s = 1/sqrt(3)
+
+ # create the vertices and faces
+ v = [(-s,-s,-s),(s,-s,-s),(s,s,-s),(-s,s,-s),(-s,-s,s),(s,-s,s),(s,s,s),(-s,s,s)]
+ faces = [[0,3,2,1],[0,1,5,4],[0,4,7,3],[6,5,1,2],[6,2,3,7],[6,7,4,5]]
+
+ # Octahedron
+ elif plato == "8":
+ # create the vertices and faces
+ v = [(1,0,0),(-1,0,0),(0,1,0),(0,-1,0),(0,0,1),(0,0,-1)]
+ faces = [[4,0,2],[4,2,1],[4,1,3],[4,3,0],[5,2,0],[5,1,2],[5,3,1],[5,0,3]]
+
+ # Dodecahedron
+ elif plato == "12":
+ # Calculate the necessary constants
+ s = 1/sqrt(3)
+ t = sqrt((3-sqrt(5))/6)
+ u = sqrt((3+sqrt(5))/6)
+
+ # create the vertices and faces
+ v = [(s,s,s),(s,s,-s),(s,-s,s),(s,-s,-s),(-s,s,s),(-s,s,-s),(-s,-s,s),(-s,-s,-s),
+ (t,u,0),(-t,u,0),(t,-u,0),(-t,-u,0),(u,0,t),(u,0,-t),(-u,0,t),(-u,0,-t),(0,t,u),
+ (0,-t,u),(0,t,-u),(0,-t,-u)]
+ faces = [[0,8,9,4,16],[0,12,13,1,8],[0,16,17,2,12],[8,1,18,5,9],[12,2,10,3,13],
+ [16,4,14,6,17],[9,5,15,14,4],[6,11,10,2,17],[3,19,18,1,13],[7,15,5,18,19],
+ [7,11,6,14,15],[7,19,3,10,11]]
+
+ # Icosahedron
+ elif plato == "20":
+ # Calculate the necessary constants
+ s = (1+sqrt(5))/2
+ t = sqrt(1+s*s)
+ s = s/t
+ t = 1/t
+
+ # create the vertices and faces
+ v = [(s,t,0),(-s,t,0),(s,-t,0),(-s,-t,0),(t,0,s),(t,0,-s),(-t,0,s),(-t,0,-s),
+ (0,s,t),(0,-s,t),(0,s,-t),(0,-s,-t)]
+ faces = [[0,8,4],[0,5,10],[2,4,9],[2,11,5],[1,6,8],[1,10,7],[3,9,6],[3,7,11],
+ [0,10,8],[1,8,10],[2,9,11],[3,11,9],[4,2,0],[5,0,2],[6,1,3],[7,3,1],
+ [8,6,4],[9,4,6],[10,5,7],[11,7,5]]
+
+ # handles faulty values of plato
+ else:
+ print("Choose keyword 'plato' from {'4','6','8','12','20'}")
+ return None
+
+ # convert the tuples to Vectors
+ verts = [Vector(i) for i in v]
+
+ return verts,faces
+
+# processes the raw data from source
+def createSolid(plato,vtrunc,etrunc,dual,snub):
+ verts = []
+ faces = []
+ edges = []
+ # the duals from each platonic solid
+ dualSource = {"4":"4",
+ "6":"8",
+ "8":"6",
+ "12":"20",
+ "20":"12"}
+
+ # constants saving space and readability
+ vtrunc *= 0.5
+ etrunc *= 0.5
+ supposed_size = 0
+ noSnub = (snub == "0") or (etrunc == 0.5) or (etrunc == 0)
+ lSnub = (snub == "L") and (0 < etrunc < 0.5)
+ rSnub = (snub == "R") and (0 < etrunc < 0.5)
+
+ # no truncation
+ if vtrunc == 0:
+ if dual: # dual is as simple as another, but mirrored platonic solid
+ vInput,fInput = source(dualSource[plato])
+ supposed_size = Asum([vInput[i] for i in fInput[0]]).length/len(fInput[0])
+ vInput = [-i*supposed_size for i in vInput] # mirror it
+ return vInput,fInput
+ return source(plato)
+ # simple truncation of the source
+ elif 0.5 >= vtrunc > 0:
+ vInput,fInput = source(plato)
+ # truncation is now equal to simple truncation of the dual of the source
+ elif vtrunc > 0.5:
+ vInput,fInput = source(dualSource[plato])
+ supposed_size = Asum([vInput[i] for i in fInput[0]]).length/len(fInput[0])
+ # account for the source being a dual
+ vtrunc = 1-vtrunc
+ if vtrunc == 0: # no truncation
+ if dual:
+ vInput,fInput = source(plato)
+ vInput = [i*supposed_size for i in vInput]
+ return vInput,fInput,sourceName
+ vInput = [-i*supposed_size for i in vInput]
+ return vInput,fInput
+
+ # generate a database for creating the faces. this exists out of a list for
+ # every vertex in the source
+ # 0 : vertex id
+ # 1 : vertices connected to this vertex, listed ccw(Counter Clock Wise)
+ # 2 : vertices generated to form the faces of this vertex
+ # 3 : faces connected to this vertex, listed ccw
+ # 4 : dictionairy containing the verts used by the connected faces
+ # 5 : list of edges that use this vertex, listed ccw
+ # 6 : dictionairy containing the verts used by the connected edges
+ v = [[i,[],[],[],{},[],{}] for i in range(len(vInput))]
+
+ # this piece of code, generates the database and the lists in ccw order
+ for x in range(len(fInput)):
+ i = fInput[x]
+ # in every faces, check which vertices connect the each vert and sort
+ # in ccw order
+ for j in range(-1,len(i)-1):
+ # only generate an edge dict, if edge truncation is needed
+ if etrunc:
+ # list edges as [min,max], to evade confusion
+ first = min([i[j-1],i[j]])
+ last = max([i[j-1],i[j]])
+ # if an edge is not allready in, add it and give the index
+ try:
+ y = edges.index([first,last])
+ except:
+ edges.append([first,last])
+ y = len(edges)-1
+ # add a dict item
+ v[i[j]][6][str(y)] = [0,0]
+ # the vertex before and after the current vertex, check whether they
+ # are allready in the database
+ after = i[j+1] not in v[i[j]][1]
+ before = i[j-1] not in v[i[j]][1]
+ # sort them and add faces and, when necessary, edges in the database
+ if after:
+ if before:
+ v[i[j]][1].append(i[j+1])
+ v[i[j]][1].append(i[j-1])
+ v[i[j]][3].append(x)
+ if etrunc: v[i[j]][5].append(y)
+ else:
+ z = v[i[j]][1].index(i[j-1])
+ v[i[j]][1].insert(z,i[j+1])
+ v[i[j]][3].insert(z,x)
+ if etrunc: v[i[j]][5].insert(z,y)
+ else:
+ z = v[i[j]][1].index(i[j+1])
+ v[i[j]][3].insert(z,x)
+ if etrunc: v[i[j]][5].insert(z,y)
+ if before:
+ v[i[j]][1].insert(z+1,i[j-1])
+ # add the current face to the current vertex in the dict
+ v[i[j]][4][str(x)] = [0,0]
+
+ # generate vert-only truncated vertices by linear interpolation
+ for i in v:
+ for j in range(len(i[1])):
+ verts.append(vInput[i[0]]*(1-vtrunc)+vInput[i[1][j]]*vtrunc)
+ l = len(verts)-1
+ # face resulting from truncating this vertex
+ i[2].append(l)
+ # this vertex is used by both faces using this edge
+ i[4][str(i[3][j])][1] = l
+ i[4][str(i[3][j-1])][0] = l
+
+ # only truncate edges when needed
+ vert_faces = []
+ if etrunc:
+ # generate a new list of vertices, by linear interpolating each vert-face
+ nVerts = []
+ for i in v:
+ f = []
+ # weird range so we dont run out of array bounds
+ for j in range(-1,len(i[2])-1):
+ # making use of the fact that the snub operation takes only
+ # one of the two vertices per edge. so rSnub only takes the
+ # first, lSnub only takes the second, and noSnub takes both
+ if rSnub or noSnub:
+ # interpolate
+ nVerts.append((1-etrunc)*verts[i[2][j]] + etrunc*verts[i[2][j-1]])
+ # add last vertex to the vert-face, face-face and edge-face
+ l = len(nVerts)-1
+ f.append(l)
+ i[4][str(i[3][j-1])][0] = l
+ i[6][str(i[5][j-1])][1] = l
+ if lSnub or noSnub:
+ # interpolate
+ nVerts.append((1-etrunc)*verts[i[2][j]] + etrunc*verts[i[2][j+1]])
+ # add last vertex to the vert-face, face-face and edge-face
+ l = len(nVerts)-1
+ f.append(l)
+ i[4][str(i[3][j])][1] = l
+ i[6][str(i[5][j-1])][0] = l
+ # add vert-face
+ vert_faces.append(f)
+
+ # snub operator creates 2 tri's instead of a planar quad, needing the
+ # next piece of code. making use of the dictionairy to create them.
+ if lSnub or rSnub:
+ edge_faces = []
+ for x in range(len(edges)):
+ one = v[edges[x][0]] # the first vertex of this edge
+ two = v[edges[x][1]] # the second
+ # using max() since the dict consists of one filled spot and one
+ # empty('cause only one vert is created)
+ f = [max(two[6][str(x)]),max(one[6][str(x)])]
+ index = one[5].index(x)
+ # create this tri from the middle line and the the previous edge
+ # on this vertex
+ if lSnub:
+ f.append(max(one[6][str(one[5][index-1])]))
+ else: # or in this case, the next
+ if index+1 >= len(one[5]): index = -1
+ f.append(max(one[6][str(one[5][index+1])]))
+ edge_faces.append(f)
+
+ # do the same for the other end of the edge
+ f = [max(one[6][str(x)]),max(two[6][str(x)])]
+ index = two[5].index(x)
+ if lSnub:
+ f.append(max(two[6][str(two[5][index-1])]))
+ else:
+ if index+1 >= len(one[5]): index = -1
+ f.append(max(two[6][str(two[5][index+1])]))
+ edge_faces.append(f)
+ else:
+ # generate edge-faces from the dictionairy, simple quads for noSnub
+ edge_faces = []
+ for i in range(len(edges)):
+ f = []
+ for j in edges[i]:
+ f.extend(v[j][6][str(i)])
+ edge_faces.append(f)
+ verts = nVerts
+ else:
+ # generate vert-faces for non-edge-truncation
+ vert_faces = [i[2] for i in v]
+
+ # calculate supposed vertex length to ensure continuity
+ if supposed_size:
+ supposed_size *= len(vert_faces[0])/Asum([verts[i] for i in vert_faces[0]]).length
+ verts = [-i*supposed_size for i in verts]
+
+ # generate face-faces by looking up the old verts and replacing them with
+ # the vertices in the dictionairy
+ face_faces = []
+ for x in range(len(fInput)):
+ f = []
+ for j in fInput[x]:
+ # again using the fact, that only one of the two verts is used
+ # for snub operation
+ if rSnub and etrunc:
+ f.append(v[j][4][str(x)][0])
+ elif lSnub and etrunc:
+ f.append(v[j][4][str(x)][1])
+ else:
+ # for cool graphics, comment the first line and uncomment the second line
+ # then work the vTrunc property, leave the other properties at 0
+ # (can also change 0 to 1 in second line to change from ccw to cw)
+ f.extend(v[j][4][str(x)]) # first
+ #f.append(v[j][4][str(x)][0]) # second
+ face_faces.append(f)
+
+ if dual:
+ # create verts by taking the average of all vertices that make up each
+ # face. do it in this order to ease the following face creation
+ nVerts = []
+ for i in vert_faces:
+ nVerts.append(Asum([verts[j] for j in i])/len(i))
+ if etrunc:
+ eStart = len(nVerts)
+ for i in edge_faces:
+ nVerts.append(Asum([verts[j] for j in i])/len(i))
+ fStart = len(nVerts)
+ for i in face_faces:
+ nVerts.append(Asum([verts[j] for j in i])/len(i))
+ # the special face generation for snub duals, it sucks, even i dont get it
+ if lSnub or rSnub:
+ for x in range(len(fInput)):
+ i = fInput[x]
+ for j in range(-1,len(i)-1):
+
+ if i[j] > i[j+1]:
+ eNext = edges.index([i[j+1],i[j]])
+ [a,b] = [1,0]
+ else:
+ eNext = edges.index([i[j],i[j+1]])
+ [a,b] = [0,1]
+ if i[j] > i[j-1]:
+ ePrev = edges.index([i[j-1],i[j]])
+ [c,d] = [0,1]
+ else:
+ ePrev = edges.index([i[j],i[j-1]])
+ [c,d] = [1,0]
+ if lSnub:
+ f = [eStart+2*eNext+b,eStart+2*eNext+a,i[j]]
+ f.append(eStart+2*ePrev+d)
+ f.append(fStart + x)
+ else:
+ f = [eStart+2*ePrev+c,eStart+2*ePrev+d,i[j]]
+ f.append(eStart+2*eNext+a)
+ f.append(fStart + x)
+ if supposed_size: faces.append(f)
+ else: faces.append(f[2:]+f[:2])
+ else:
+ # for noSnub situations, the face generation is somewhat easier.
+ # first calculate what order faces must be added to ensure convex solids
+ # this by calculating the angle between the middle of the four vertices
+ # and the first face. if the face is above the middle, use that diagonal
+ # otherwise use the other diagonal
+ if etrunc:
+ f = [v[0][0],eStart+v[0][5][-1],fStart+v[0][3][0],eStart+v[0][5][0]]
+ else:
+ f = [v[0][0],fStart+v[0][3][0],v[0][1][0],fStart+v[0][3][-1]]
+ p = [nVerts[i] for i in f]
+ mid = 0.25*Asum(p)
+ norm = (p[1]-p[0]).cross(p[2]-p[0])
+ dot = norm.dot(mid-p[0])/(norm.length*(mid-p[0]).length)
+ tollerance = 0.001 # ~ cos(0.06 degrees)
+ if ((dot > tollerance) and (not supposed_size)) or ((dot < -tollerance) and (supposed_size)):
+ direction = 1 # first diagonal
+ elif ((dot < -tollerance) and (not supposed_size)) or ((dot > tollerance) and (supposed_size)):
+ direction = -1 # second diagonal
+ else:
+ direction = 0 # no diagonal, face is planar (somewhat)
+
+ if etrunc: # for every vertex
+ for i in v: # add the face, consisting of the vert,edge,next
+ # edge and face between those edges
+ for j in range(len(i[1])):
+ f = [i[0],eStart+i[5][j-1],fStart+i[3][j],eStart+i[5][j]]
+ if direction == 1: # first diagonal
+ faces.extend([[f[0],f[1],f[3]],[f[1],f[2],f[3]]])
+ elif direction == -1: # first diagonal
+ faces.extend([[f[0],f[1],f[2]],[f[0],f[2],f[3]]])
+ else:
+ faces.append(f) # no diagonal
+ else:
+ for i in v: # for every vertex
+ for j in range(len(i[1])):
+ if i[0] < i[1][j]: # face consists of vert, vert on other
+ # end of edge and both faces using that
+ # edge, so exclude verts allready used
+ f = [i[0],fStart+i[3][j], i[1][j],fStart+i[3][j-1]]
+ if direction == -1: # secong diagonal
+ faces.extend([[f[0],f[1],f[3]],[f[1],f[2],f[3]]])
+ elif direction == 1: # first diagonal
+ faces.extend([[f[0],f[1],f[2]],[f[0],f[2],f[3]]])
+ else:
+ faces.append(f) # no diagonal
+ verts = nVerts # use new vertices
+ else:
+ # concatenate all faces, since they dont have to be used sepperately anymore
+ faces = face_faces
+ if etrunc: faces += edge_faces
+ faces += vert_faces
+
+ return verts,faces
+
+
+class Solids(bpy.types.Operator):
+ """Add one of the (regular) solids (mesh)"""
+ bl_idname = "mesh.primitive_solid_add"
+ bl_label = "(Regular) solids"
+ bl_description = "Add one of the platoic or archimedean solids"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ source = EnumProperty(items = (("4","Tetrahedron",""),
+ ("6","Hexahedron",""),
+ ("8","Octahedron",""),
+ ("12","Dodecahedron",""),
+ ("20","Icosahedron","")),
+ name = "Source",
+ description = "Starting point of your solid")
+ size = FloatProperty(name = "Size",
+ description = "Radius of the sphere through the vertices",
+ min = 0.01,
+ soft_min = 0.01,
+ max = 100,
+ soft_max = 100,
+ default = 1.0)
+ vTrunc = FloatProperty(name = "Vertex Truncation",
+ description = "Ammount of vertex truncation",
+ min = 0.0,
+ soft_min = 0.0,
+ max = 2.0,
+ soft_max = 2.0,
+ default = 0.0,
+ precision = 3,
+ step = 0.5)
+ eTrunc = FloatProperty(name = "Edge Truncation",
+ description = "Ammount of edge truncation",
+ min = 0.0,
+ soft_min = 0.0,
+ max = 1.0,
+ soft_max = 1.0,
+ default = 0.0,
+ precision = 3,
+ step = 0.2)
+ snub = EnumProperty(items = (("0","No Snub",""),
+ ("L","Left Snub",""),
+ ("R","Right Snub","")),
+ name = "Snub",
+ description = "Create the snub version")
+ dual = BoolProperty(name="Dual",
+ description="Create the dual of the current solid",
+ default=False)
+ keepSize = BoolProperty(name="Keep Size",
+ description="Keep the whole solid at a constant size",
+ default=False)
+ preset = EnumProperty(items = (("0","Custom",""),
+ ("t4","Truncated Tetrahedron",""),
+ ("r4","Cuboctahedron",""),
+ ("t6","Truncated Cube",""),
+ ("t8","Truncated Octahedron",""),
+ ("b6","Rhombicuboctahedron",""),
+ ("c6","Truncated Cuboctahedron",""),
+ ("s6","Snub Cube",""),
+ ("r12","Icosidodecahedron",""),
+ ("t12","Truncated Dodecahedron",""),
+ ("t20","Truncated Icosahedron",""),
+ ("b12","Rhombicosidodecahedron",""),
+ ("c12","Truncated Icosidodecahedron",""),
+ ("s12","Snub Dodecahedron",""),
+ ("dt4","Triakis Tetrahedron",""),
+ ("dr4","Rhombic Dodecahedron",""),
+ ("dt6","Triakis Octahedron",""),
+ ("dt8","Triakis Hexahedron",""),
+ ("db6","Deltoidal Icositetrahedron",""),
+ ("dc6","Disdyakis Dodecahedron",""),
+ ("ds6","Pentagonal Icositetrahedron",""),
+ ("dr12","Rhombic Triacontahedron",""),
+ ("dt12","Triakis Icosahedron",""),
+ ("dt20","Pentakis Dodecahedron",""),
+ ("db12","Deltoidal Hexecontahedron",""),
+ ("dc12","Disdyakis Triacontahedron",""),
+ ("ds12","Pentagonal Hexecontahedron",""),
+ ("c","Cube",""),
+ ("sb","Soccer ball","")),
+ name = "Presets",
+ description = "Parameters for some hard names")
+
+ # actual preset values
+ p = {"t4":["4",2/3,0,0,"0"],
+ "r4":["4",1,1,0,"0"],
+ "t6":["6",2/3,0,0,"0"],
+ "t8":["8",2/3,0,0,"0"],
+ "b6":["6",1.0938,1,0,"0"],
+ "c6":["6",1.0572,0.585786,0,"0"],
+ "s6":["6",1.0875,0.704,0,"L"],
+ "r12":["12",1,0,0,"0"],
+ "t12":["12",2/3,0,0,"0"],
+ "t20":["20",2/3,0,0,"0"],
+ "b12":["12",1.1338,1,0,"0"],
+ "c12":["20",0.921,0.553,0,"0"],
+ "s12":["12",1.1235,0.68,0,"L"],
+ "dt4":["4",2/3,0,1,"0"],
+ "dr4":["4",1,2/3,1,"0"],
+ "dt6":["6",4/3,0,1,"0"],
+ "dt8":["8",1,0,1,"0"],
+ "db6":["6",1.0938,0.756,1,"0"],
+ "dc6":["6",1,1,1,"0"],
+ "ds6":["6",1.0875,0.704,1,"L"],
+ "dr12":["12",1.54,0,1,"0"],
+ "dt12":["12",5/3,0,1,"0"],
+ "dt20":["20",2/3,0,1,"0"],
+ "db12":["12",1,0.912,1,"0"],
+ "dc12":["20",0.921,1,1,"0"],
+ "ds12":["12",1.1235,0.68,1,"L"],
+ "c":["6",0,0,0,"0"],
+ "sb":["20",2/3,0,0,"0"]}
+
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+ def execute(self,context):
+ # turn off undo for better performance (3 - 5x faster), also makes sure
+ # that mesh ops are undoable and entire script acts as one operator
+ bpy.context.user_preferences.edit.global_undo = False
+
+ props = self.properties
+
+ #if preset, set preset
+ if props.preset != "0":
+ using = self.p[props.preset]
+ props.source = using[0]
+ props.vTrunc = using[1]
+ props.eTrunc = using[2]
+ props.dual = using[3]
+ props.snub = using[4]
+ props.preset = "0"
+
+ # generate mesh
+ verts,faces = createSolid(props.source,
+ props.vTrunc,
+ props.eTrunc,
+ props.dual,
+ props.snub)
+
+ # turn n-gons in quads and tri's
+ faces = createPolys(faces)
+
+ # resize to normal size, or if keepSize, make sure all verts are of length 'size'
+ if props.keepSize:
+ rad = props.size/verts[0].length
+ else: rad = props.size
+ verts = [i*rad for i in verts]
+
+ # generate object
+ obj = create_mesh_object(context,verts,[],faces,"Solid",props.edit)
+
+ # vertices will be on top of each other in some cases,
+ # so remove doubles then
+ if ((props.vTrunc == 1) and (props.eTrunc == 0)) or (props.eTrunc == 1):
+ current_mode = obj.mode
+ if current_mode == 'OBJECT':
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.remove_doubles()
+ bpy.ops.object.mode_set(mode=current_mode)
+
+ # snub duals suck, so make all normals point outwards
+ if props.dual and (props.snub != "0"):
+ current_mode = obj.mode
+ if current_mode == 'OBJECT':
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.normals_make_consistent()
+ bpy.ops.object.mode_set(mode=current_mode)
+
+ # turn undo back on
+ bpy.context.user_preferences.edit.global_undo = True
+
+ return {'FINISHED'}
+
+class Solids_add_menu(bpy.types.Menu):
+ """Define the menu with presets"""
+ bl_idname = "Solids_add_menu"
+ bl_label = "Solids"
+
+ def draw(self,context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator(Solids.bl_idname, text = "Solid")
+ layout.menu(PlatonicMenu.bl_idname, text = "Platonic")
+ layout.menu(ArchiMenu.bl_idname, text = "Archimeadean")
+ layout.menu(CatalanMenu.bl_idname, text = "Catalan")
+ layout.menu(OtherMenu.bl_idname, text = "Others")
+
+class PlatonicMenu(bpy.types.Menu):
+ """Define Platonic menu"""
+ bl_idname = "Platonic_calls"
+ bl_label = "Platonic"
+
+ def draw(self,context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator(Solids.bl_idname, text = "Tetrahedron").source = "4"
+ layout.operator(Solids.bl_idname, text = "Hexahedron").source = "6"
+ layout.operator(Solids.bl_idname, text = "Octahedron").source = "8"
+ layout.operator(Solids.bl_idname, text = "Dodecahedron").source = "12"
+ layout.operator(Solids.bl_idname, text = "Icosahedron").source = "20"
+
+class ArchiMenu(bpy.types.Menu):
+ """Defines Achimedean preset menu"""
+ bl_idname = "Achimedean_calls"
+ bl_label = "Archimedean"
+
+ def draw(self,context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator(Solids.bl_idname, text = "Truncated Tetrahedron").preset = "t4"
+ layout.operator(Solids.bl_idname, text = "Cuboctahedron").preset = "r4"
+ layout.operator(Solids.bl_idname, text = "Truncated Cube").preset = "t6"
+ layout.operator(Solids.bl_idname, text = "Truncated Octahedron").preset = "t8"
+ layout.operator(Solids.bl_idname, text = "Rhombicuboctahedron").preset = "b6"
+ layout.operator(Solids.bl_idname, text = "Truncated Cuboctahedron").preset = "c6"
+ layout.operator(Solids.bl_idname, text = "Snub Cube").preset = "s6"
+ layout.operator(Solids.bl_idname, text = "Icosidodecahedron").preset = "r12"
+ layout.operator(Solids.bl_idname, text = "Truncated Dodecahedron").preset = "t12"
+ layout.operator(Solids.bl_idname, text = "Truncated Icosahedron").preset = "t20"
+ layout.operator(Solids.bl_idname, text = "Rhombicosidodecahedron").preset = "b12"
+ layout.operator(Solids.bl_idname, text = "Truncated Icosidodecahedron").preset = "c12"
+ layout.operator(Solids.bl_idname, text = "Snub Dodecahedron").preset = "s12"
+
+class CatalanMenu(bpy.types.Menu):
+ """Defines Catalan preset menu"""
+ bl_idname = "Catalan_calls"
+ bl_label = "Catalan"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator(Solids.bl_idname, text = "Triakis Tetrahedron").preset = "dt4"
+ layout.operator(Solids.bl_idname, text = "Rhombic Dodecahedron").preset = "dr4"
+ layout.operator(Solids.bl_idname, text = "Triakis Octahedron").preset = "dt6"
+ layout.operator(Solids.bl_idname, text = "Triakis Hexahedron").preset = "dt8"
+ layout.operator(Solids.bl_idname, text = "Deltoidal Icositetrahedron").preset = "db6"
+ layout.operator(Solids.bl_idname, text = "Disdyakis Dodecahedron").preset = "dc6"
+ layout.operator(Solids.bl_idname, text = "Pentagonal Icositetrahedron").preset = "ds6"
+ layout.operator(Solids.bl_idname, text = "Rhombic Triacontahedron").preset = "dr12"
+ layout.operator(Solids.bl_idname, text = "Triakis Icosahedron").preset = "dt12"
+ layout.operator(Solids.bl_idname, text = "Pentakis Dodecahedron").preset = "dt20"
+ layout.operator(Solids.bl_idname, text = "Deltoidal Hexecontahedron").preset = "dt20"
+ layout.operator(Solids.bl_idname, text = "Disdyakis Triacontahedron").preset = "db12"
+ layout.operator(Solids.bl_idname, text = "Pentagonal Hexecontahedron").preset = "ds12"
+
+class OtherMenu(bpy.types.Menu):
+ """Defines Others preset menu"""
+ bl_idname = "Others_calls"
+ bl_label = "Others"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator(Solids.bl_idname, text = "Cube").preset = "c"
+ layout.operator(Solids.bl_idname, text = "Soccer ball").preset = "sb"
+
+
+import space_info
+
+classes = [
+ Solids,
+ Solids_add_menu,
+ PlatonicMenu,
+ ArchiMenu,
+ CatalanMenu,
+ OtherMenu
+]
+
+menu_func = (lambda self,
+ context: self.layout.menu(Solids_add_menu.bl_idname, icon="PLUGIN"))
+
+def register():
+ for i in classes:
+ bpy.types.register(i)
+ space_info.INFO_MT_mesh_add.append(menu_func)
+
+def unregister():
+ for i in classes:
+ bpy.types.unregister(i)
+ space_info.INFO_MT_mesh_add.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/add_mesh_twisted_torus.py b/add_mesh_twisted_torus.py
new file mode 100644
index 00000000..e2f5b89f
--- /dev/null
+++ b/add_mesh_twisted_torus.py
@@ -0,0 +1,366 @@
+# add_mesh_twisted_torus.py Copyright (C) 2009-2010, Paulo Gomes
+# tuga3d {at} gmail {dot} com
+# add twisted torus to the blender 2.50 add->mesh menu
+# ***** 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 *****
+
+"""
+
+Usage:
+
+* Launch from Add Mesh menu
+
+* Modify parameters as desired or keep defaults
+
+"""
+
+bl_addon_info = {
+ 'name': 'Add Mesh: Twisted Torus',
+ 'author': 'Paulo_Gomes',
+ 'version': '0.11',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Mesh ',
+ 'description': 'Adds a mesh Twisted Torus to the Add Mesh menu',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Add_Mesh/Add_Twisted_Torus',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21622&group_id=153&atid=469',
+ 'category': 'Add Mesh'}
+
+import bpy
+from bpy.props import *
+
+import mathutils
+from mathutils import *
+from math import cos, sin, pi
+
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = TranslationMatrix(context.scene.cursor_location)
+ obj_align = context.user_preferences.edit.object_align
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ rot = context.space_data.region_3d.view_matrix.rotation_part().invert().resize4x4()
+ else:
+ rot = Matrix()
+ align_matrix = loc * rot
+ return align_matrix
+
+
+# Create a new mesh (object) from verts/edges/faces.
+# verts/edges/faces ... List of vertices/edges/faces for the
+# new mesh (as used in from_pydata).
+# name ... Name of the new mesh (& object).
+# edit ... Replace existing mesh data.
+# Note: Using "edit" will destroy/delete existing mesh data.
+def create_mesh_object(context, verts, edges, faces, name, edit, align_matrix):
+ scene = context.scene
+ obj_act = scene.objects.active
+
+ # Can't edit anything, unless we have an active obj.
+ if edit and not obj_act:
+ return None
+
+ # Create new mesh
+ mesh = bpy.data.meshes.new(name)
+
+ # Make a mesh from a list of verts/edges/faces.
+ mesh.from_pydata(verts, edges, faces)
+
+ # Update mesh geometry after adding stuff.
+ mesh.update()
+
+ # Deselect all objects.
+ bpy.ops.object.select_all(action='DESELECT')
+
+ if edit:
+ # Replace geometry of existing object
+
+ # Use the active obj and select it.
+ ob_new = obj_act
+ ob_new.select = True
+
+ if obj_act.mode == 'OBJECT':
+ # Get existing mesh datablock.
+ old_mesh = ob_new.data
+
+ # Set object data to nothing
+ ob_new.data = None
+
+ # Clear users of existing mesh datablock.
+ old_mesh.user_clear()
+
+ # Remove old mesh datablock if no users are left.
+ if (old_mesh.users == 0):
+ bpy.data.meshes.remove(old_mesh)
+
+ # Assign new mesh datablock.
+ ob_new.data = mesh
+
+ else:
+ # Create new object
+ ob_new = bpy.data.objects.new(name, mesh)
+
+ # Link new object to the given scene and select it.
+ scene.objects.link(ob_new)
+ ob_new.select = True
+
+ # Place the object at the 3D cursor location.
+ # apply viewRotaion
+ ob_new.matrix_world = align_matrix
+
+ if obj_act and obj_act.mode == 'EDIT':
+ if not edit:
+ # We are in EditMode, switch to ObjectMode.
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Select the active object as well.
+ obj_act.select = True
+
+ # Apply location of new object.
+ scene.update()
+
+ # Join new object into the active.
+ bpy.ops.object.join()
+
+ # Switching back to EditMode.
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ ob_new = obj_act
+
+ else:
+ # We are in ObjectMode.
+ # Make the new object the active one.
+ scene.objects.active = ob_new
+
+ return ob_new
+
+
+# A very simple "bridge" tool.
+# Connects two equally long vertex rows with faces.
+# Returns a list of the new faces (list of lists)
+#
+# vertIdx1 ... First vertex list (list of vertex indices).
+# vertIdx2 ... Second vertex list (list of vertex indices).
+# closed ... Creates a loop (first & last are closed).
+# flipped ... Invert the normal of the face(s).
+#
+# Note: You can set vertIdx1 to a single vertex index to create
+# a fan/star of faces.
+# Note: If both vertex idx list are the same length they have
+# to have at least 2 vertices.
+def createFaces(vertIdx1, vertIdx2, closed=False, flipped=False):
+ faces = []
+
+ if not vertIdx1 or not vertIdx2:
+ return None
+
+ if len(vertIdx1) < 2 and len(vertIdx2) < 2:
+ return None
+
+ fan = False
+ if (len(vertIdx1) != len(vertIdx2)):
+ if (len(vertIdx1) == 1 and len(vertIdx2) > 1):
+ fan = True
+ else:
+ return None
+
+ total = len(vertIdx2)
+
+ if closed:
+ # Bridge the start with the end.
+ if flipped:
+ face = [
+ vertIdx1[0],
+ vertIdx2[0],
+ vertIdx2[total - 1]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ faces.append(face)
+
+ else:
+ face = [vertIdx2[0], vertIdx1[0]]
+ if not fan:
+ face.append(vertIdx1[total - 1])
+ face.append(vertIdx2[total - 1])
+ faces.append(face)
+
+ # Bridge the rest of the faces.
+ for num in range(total - 1):
+ if flipped:
+ if fan:
+ face = [vertIdx2[num], vertIdx1[0], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx2[num], vertIdx1[num],
+ vertIdx1[num + 1], vertIdx2[num + 1]]
+ faces.append(face)
+ else:
+ if fan:
+ face = [vertIdx1[0], vertIdx2[num], vertIdx2[num + 1]]
+ else:
+ face = [vertIdx1[num], vertIdx2[num],
+ vertIdx2[num + 1], vertIdx1[num + 1]]
+ faces.append(face)
+
+ return faces
+
+
+def add_twisted_torus(major_rad, minor_rad, major_seg, minor_seg, twists):
+ PI_2 = pi * 2.0
+ z_axis = (0.0, 0.0, 1.0)
+
+ verts = []
+ faces = []
+
+ edgeloop_prev = []
+ for major_index in range(major_seg):
+ quat = Quaternion(z_axis, (major_index / major_seg) * PI_2)
+ rot_twists = PI_2 * major_index / major_seg * twists
+
+ edgeloop = []
+
+ # Create section ring
+ for minor_index in range(minor_seg):
+ angle = (PI_2 * minor_index / minor_seg) + rot_twists
+
+ vec = Vector((
+ major_rad + (cos(angle) * minor_rad),
+ 0.0,
+ sin(angle) * minor_rad))
+ vec = vec * quat
+
+ edgeloop.append(len(verts))
+ verts.append(vec)
+
+ # Remember very first edgeloop.
+ if major_index == 0:
+ edgeloop_first = edgeloop
+
+ # Bridge last with current ring
+ if edgeloop_prev:
+ f = createFaces(edgeloop_prev, edgeloop, closed=True)
+ faces.extend(f)
+
+ edgeloop_prev = edgeloop
+
+ # Bridge first and last ring
+ f = createFaces(edgeloop_prev, edgeloop_first, closed=True)
+ faces.extend(f)
+
+ return verts, faces
+
+
+class AddTwistedTorus(bpy.types.Operator):
+ '''Add a torus mesh'''
+ bl_idname = "mesh.primitive_twisted_torus_add"
+ bl_label = "Add Torus"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+ major_radius = FloatProperty(name="Major Radius",
+ description="Radius from the origin to the" \
+ " center of the cross section",
+ min=0.01,
+ max=100.0,
+ default=1.0)
+ minor_radius = FloatProperty(name="Minor Radius",
+ description="Radius of the torus' cross section",
+ min=0.01,
+ max=100.0,
+ default=0.25)
+ major_segments = IntProperty(name="Major Segments",
+ description="Number of segments for the main ring of the torus",
+ min=3,
+ max=256,
+ default=48)
+ minor_segments = IntProperty(name="Minor Segments",
+ description="Number of segments for the minor ring of the torus",
+ min=3,
+ max=256,
+ default=12)
+ twists = IntProperty(name="Twists",
+ description="Number of twists of the torus",
+ min=0,
+ max=256,
+ default=1)
+
+ use_abso = BoolProperty(name="Use Int+Ext Controls",
+ description="Use the Int / Ext controls for torus dimensions",
+ default=False)
+ abso_major_rad = FloatProperty(name="Exterior Radius",
+ description="Total Exterior Radius of the torus",
+ min=0.01,
+ max=100.0,
+ default=1.0)
+ abso_minor_rad = FloatProperty(name="Inside Radius",
+ description="Total Interior Radius of the torus",
+ min=0.01,
+ max=100.0,
+ default=0.5)
+ align_matrix = Matrix()
+
+ def execute(self, context):
+ props = self.properties
+
+ if props.use_abso == True:
+ extra_helper = (props.abso_major_rad - props.abso_minor_rad) * 0.5
+ props.major_radius = props.abso_minor_rad + extra_helper
+ props.minor_radius = extra_helper
+
+ verts, faces = add_twisted_torus(
+ props.major_radius,
+ props.minor_radius,
+ props.major_segments,
+ props.minor_segments,
+ props.twists)
+
+ # Actually create the mesh object from this geometry data.
+ obj = create_mesh_object(context, verts, [], faces, "TwistedTorus",
+ props.edit, self.align_matrix)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+# Add to the menu
+menu_func = (lambda self,
+ context: self.layout.operator(AddTwistedTorus.bl_idname,
+ text="Twisted Torus", icon='MESH_DONUT'))
+
+
+def register():
+ bpy.types.register(AddTwistedTorus)
+ bpy.types.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ bpy.types.unregister(AddTwistedTorus)
+ bpy.types.INFO_MT_mesh_add.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/curve_simplify.py b/curve_simplify.py
new file mode 100644
index 00000000..175fffb9
--- /dev/null
+++ b/curve_simplify.py
@@ -0,0 +1,597 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+This script simplifies Curves.
+"""
+
+bl_addon_info = {
+ 'name': 'Curve: simplify curves',
+ 'author': 'testscreenings',
+ 'version': '1',
+ 'blender': (2, 5, 3),
+ 'location': 'Toolshelf > search > simplify curves',
+ 'description': 'This script simplifies 3D curves and fcurves',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Curve/Curve_Simplify',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22327&group_id=153&atid=468',
+ 'category': 'Add Curve'}
+
+
+####################################################
+import bpy
+from bpy.props import *
+import mathutils
+import math
+
+##############################
+#### simplipoly algorithm ####
+##############################
+# get SplineVertIndicies to keep
+def simplypoly(splineVerts, options):
+ # main vars
+ newVerts = [] # list of vertindices to keep
+ points = splineVerts # list of 3dVectors
+ pointCurva = [] # table with curvatures
+ curvatures = [] # averaged curvatures per vert
+ for p in points:
+ pointCurva.append([])
+ order = options[3] # order of sliding beziercurves
+ k_thresh = options[2] # curvature threshold
+ dis_error = options[6] # additional distance error
+
+ # get curvatures per vert
+ for i, point in enumerate(points[:-(order-1)]):
+ BVerts = points[i:i+order]
+ for b, BVert in enumerate(BVerts[1:-1]):
+ deriv1 = getDerivative(BVerts, 1/(order-1), order-1)
+ deriv2 = getDerivative(BVerts, 1/(order-1), order-2)
+ curva = getCurvature(deriv1, deriv2)
+ pointCurva[i+b+1].append(curva)
+
+ # average the curvatures
+ for i in range(len(points)):
+ avgCurva = sum(pointCurva[i]) / (order-1)
+ curvatures.append(avgCurva)
+
+ # get distancevalues per vert - same as Ramer-Douglas-Peucker
+ # but for every vert
+ distances = [0.0] #first vert is always kept
+ for i, point in enumerate(points[1:-1]):
+ dist = altitude(points[i], points[i+2], points[i+1])
+ distances.append(dist)
+ distances.append(0.0) # last vert is always kept
+
+ # generate list of vertindicies to keep
+ # tested against averaged curvatures and distances of neighbour verts
+ newVerts.append(0) # first vert is always kept
+ for i, curv in enumerate(curvatures):
+ if (curv >= k_thresh*0.01
+ or distances[i] >= dis_error*0.1):
+ newVerts.append(i)
+ newVerts.append(len(curvatures)-1) # last vert is always kept
+
+ return newVerts
+
+# get binomial coefficient
+def binom(n, m):
+ b = [0] * (n+1)
+ b[0] = 1
+ for i in range(1, n+1):
+ b[i] = 1
+ j = i-1
+ while j > 0:
+ b[j] += b[j-1]
+ j-= 1
+ return b[m]
+
+# get nth derivative of order(len(verts)) bezier curve
+def getDerivative(verts, t, nth):
+ order = len(verts) - 1 - nth
+ QVerts = []
+
+ if nth:
+ for i in range(nth):
+ if QVerts:
+ verts = QVerts
+ derivVerts = []
+ for i in range(len(verts)-1):
+ derivVerts.append(verts[i+1] - verts[i])
+ QVerts = derivVerts
+ else:
+ QVerts = verts
+
+ if len(verts[0]) == 3:
+ point = mathutils.Vector((0, 0, 0))
+ if len(verts[0]) == 2:
+ point = mathutils.Vector((0, 0))
+
+ for i, vert in enumerate(QVerts):
+ point += binom(order, i) * math.pow(t, i) * math.pow(1-t, order-i) * vert
+ deriv = point
+
+ return deriv
+
+# get curvature from first, second derivative
+def getCurvature(deriv1, deriv2):
+ if deriv1.length == 0: # in case of points in straight line
+ curvature = 0
+ return curvature
+ curvature = (deriv1.cross(deriv2)).length / math.pow(deriv1.length, 3)
+ return curvature
+
+#########################################
+#### Ramer-Douglas-Peucker algorithm ####
+#########################################
+# get altitude of vert
+def altitude(point1, point2, pointn):
+ edge1 = point2 - point1
+ edge2 = pointn - point1
+ if edge2.length == 0:
+ altitude = 0
+ return altitude
+ if edge1.length == 0:
+ altitude = edge2.length
+ return altitude
+ alpha = edge1.angle(edge2)
+ altitude = math.sin(alpha) * edge2.length
+ return altitude
+
+# iterate through verts
+def iterate(points, newVerts, error):
+ new = []
+ for newIndex in range(len(newVerts)-1):
+ bigVert = 0
+ alti_store = 0
+ for i, point in enumerate(points[newVerts[newIndex]+1:newVerts[newIndex+1]]):
+ alti = altitude(points[newVerts[newIndex]], points[newVerts[newIndex+1]], point)
+ if alti > alti_store:
+ alti_store = alti
+ if alti_store >= error:
+ bigVert = i+1+newVerts[newIndex]
+ if bigVert:
+ new.append(bigVert)
+ if new == []:
+ return False
+ return new
+
+#### get SplineVertIndicies to keep
+def simplify_RDP(splineVerts, options):
+ #main vars
+ error = options[4]
+
+ # set first and last vert
+ newVerts = [0, len(splineVerts)-1]
+
+ # iterate through the points
+ new = 1
+ while new != False:
+ new = iterate(splineVerts, newVerts, error)
+ if new:
+ newVerts += new
+ newVerts.sort()
+ return newVerts
+
+##########################
+#### CURVE GENERATION ####
+##########################
+# set bezierhandles to auto
+def setBezierHandles(newCurve):
+ scene = bpy.context.scene
+ bpy.ops.object.mode_set(mode='EDIT', toggle=True)
+ bpy.ops.curve.select_all(action='SELECT')
+ bpy.ops.curve.handle_type_set(type='AUTOMATIC')
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=True)
+
+# get array of new coords for new spline from vertindices
+def vertsToPoints(newVerts, splineVerts, splineType):
+ # main vars
+ newPoints = []
+
+ # array for BEZIER spline output
+ if splineType == 'BEZIER':
+ for v in newVerts:
+ newPoints += splineVerts[v].to_tuple()
+
+ # array for nonBEZIER output
+ else:
+ for v in newVerts:
+ newPoints += (splineVerts[v].to_tuple())
+ if splineType == 'NURBS':
+ newPoints.append(1) #for nurbs w=1
+ else: #for poly w=0
+ newPoints.append(0)
+ return newPoints
+
+#########################
+#### MAIN OPERATIONS ####
+#########################
+
+def main(context, obj, options):
+ #print("\n_______START_______")
+ # main vars
+ mode = options[0]
+ output = options[1]
+ degreeOut = options[5]
+ keepShort = options[7]
+ bpy.ops.object.select_all(action='DESELECT')
+ scene = context.scene
+ splines = obj.data.splines.values()
+
+ # create curvedatablock
+ curve = bpy.data.curves.new("simple_"+obj.name, type = 'CURVE')
+
+ # go through splines
+ for spline_i, spline in enumerate(splines):
+ # test if spline is a long enough
+ if len(spline.points) >= 7 or keepShort:
+ #check what type of spline to create
+ if output == 'INPUT':
+ splineType = spline.type
+ else:
+ splineType = output
+
+ # get vec3 list to simplify
+ if spline.type == 'BEZIER': # get bezierverts
+ splineVerts = [splineVert.co.copy()
+ for splineVert in spline.bezier_points.values()]
+
+ else: # verts from all other types of curves
+ splineVerts = [splineVert.co.copy().resize3D()
+ for splineVert in spline.points.values()]
+
+ # simplify spline according to mode
+ if mode == 'distance':
+ newVerts = simplify_RDP(splineVerts, options)
+
+ if mode == 'curvature':
+ newVerts = simplypoly(splineVerts, options)
+
+ # convert indicies into vectors3D
+ newPoints = vertsToPoints(newVerts, splineVerts, splineType)
+
+ # create new spline
+ newSpline = curve.splines.new(type = splineType)
+
+ # put newPoints into spline according to type
+ if splineType == 'BEZIER':
+ newSpline.bezier_points.add(int(len(newPoints)*0.33))
+ newSpline.bezier_points.foreach_set('co', newPoints)
+ else:
+ newSpline.points.add(int(len(newPoints)*0.25 - 1))
+ newSpline.points.foreach_set('co', newPoints)
+
+ # set degree of outputNurbsCurve
+ if output == 'NURBS':
+ newSpline.order_u = degreeOut
+
+ # splineoptions
+ newSpline.endpoint_u = spline.endpoint_u
+
+ # create ne object and put into scene
+ newCurve = bpy.data.objects.new("simple_"+obj.name, curve)
+ scene.objects.link(newCurve)
+ newCurve.select = True
+ scene.objects.active = newCurve
+ newCurve.matrix_world = obj.matrix_world
+
+ # set bezierhandles to auto
+ setBezierHandles(newCurve)
+
+ #print("________END________\n")
+ return
+
+##################
+## get preoperator fcurves
+def getFcurveData(obj):
+ fcurves = []
+ for fc in obj.animation_data.action.fcurves:
+ if fc.select:
+ fcVerts = [vcVert.co.copy().resize3D()
+ for vcVert in fc.keyframe_points.values()]
+ fcurves.append(fcVerts)
+ return fcurves
+
+def selectedfcurves(obj):
+ fcurves_sel = []
+ for i, fc in enumerate(obj.animation_data.action.fcurves):
+ if fc.select:
+ fcurves_sel.append(fc)
+ return fcurves_sel
+
+###########################################################
+## fCurves Main
+def fcurves_simplify(context, obj, options, fcurves):
+ # main vars
+ mode = options[0]
+ scene = context.scene
+ fcurves_obj = obj.animation_data.action.fcurves
+
+ #get indicies of selected fcurves
+ fcurve_sel = selectedfcurves(obj)
+
+ # go through fcurves
+ for fcurve_i, fcurve in enumerate(fcurves):
+ # test if fcurve is long enough
+ if len(fcurve) >= 7:
+
+ # simplify spline according to mode
+ if mode == 'distance':
+ newVerts = simplify_RDP(fcurve, options)
+
+ if mode == 'curvature':
+ newVerts = simplypoly(fcurve, options)
+
+ # convert indicies into vectors3D
+ newPoints = []
+
+ #this is different from the main() function for normal curves, different api...
+ for v in newVerts:
+ newPoints.append(fcurve[v])
+
+ #remove all points from curve first
+ for i in range(len(fcurve)-1,0,-1):
+ fcurve_sel[fcurve_i].keyframe_points.remove(fcurve_sel[fcurve_i].keyframe_points[i])
+ # put newPoints into fcurve
+ for v in newPoints:
+ fcurve_sel[fcurve_i].keyframe_points.add(frame=v[0],value=v[1])
+ #fcurve.points.foreach_set('co', newPoints)
+ return
+
+#################################################
+#### ANIMATION CURVES OPERATOR ##################
+#################################################
+class GRAPH_OT_simplify(bpy.types.Operator):
+ ''''''
+ bl_idname = "graph.simplify"
+ bl_label = "simplifiy f-curves"
+ bl_description = "simplify selected f-curves"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ ## Properties
+ opModes = [
+ ('distance', 'distance', 'distance'),
+ ('curvature', 'curvature', 'curvature')]
+ mode = EnumProperty(name="Mode",
+ description="choose algorithm to use",
+ items=opModes)
+ k_thresh = FloatProperty(name="k",
+ min=0, soft_min=0,
+ default=0, precision=3,
+ description="threshold")
+ pointsNr = IntProperty(name="n",
+ min=5, soft_min=5,
+ max=16, soft_max=9,
+ default=5,
+ description="degree of curve to get averaged curvatures")
+ error = FloatProperty(name="error",
+ description="maximum error to allow - distance",
+ min=0.0, soft_min=0.0,
+ default=0, precision=3)
+ degreeOut = IntProperty(name="degree",
+ min=3, soft_min=3,
+ max=7, soft_max=7,
+ default=5,
+ description="degree of new curve")
+ dis_error = FloatProperty(name="distance error",
+ description="maximum error in Blenderunits to allow - distance",
+ min=0, soft_min=0,
+ default=0.0, precision=3)
+ fcurves = []
+
+ ''' Remove curvature mode as long as it isnn't significantly improved
+
+ def draw(self, context):
+ props = self.properties
+ layout = self.layout
+ col = layout.column()
+ col.label('Mode:')
+ col.prop(props, 'mode', expand=True)
+ if self.properties.mode == 'distance':
+ box = layout.box()
+ box.label(props.mode, icon='ARROW_LEFTRIGHT')
+ box.prop(props, 'error', expand=True)
+ if self.properties.mode == 'curvature':
+ box = layout.box()
+ box.label('degree', icon='SMOOTHCURVE')
+ box.prop(props, 'pointsNr', expand=True)
+ box.label('threshold', icon='PARTICLE_PATH')
+ box.prop(props, 'k_thresh', expand=True)
+ box.label('distance', icon='ARROW_LEFTRIGHT')
+ box.prop(props, 'dis_error', expand=True)
+ col = layout.column()
+ '''
+
+ def draw(self, context):
+ props = self.properties
+ layout = self.layout
+ col = layout.column()
+ col.prop(props, 'error', expand=True)
+
+ ## Check for animdata
+ def poll(self, context):
+ obj = context.active_object
+ fcurves = False
+ if obj:
+ animdata = obj.animation_data
+ if animdata:
+ act = animdata.action
+ if act:
+ fcurves = act.fcurves
+ return (obj and fcurves)
+
+ ## execute
+ def execute(self, context):
+ #print("------START------")
+
+ options = [
+ self.properties.mode, #0
+ self.properties.mode, #1
+ self.properties.k_thresh, #2
+ self.properties.pointsNr, #3
+ self.properties.error, #4
+ self.properties.degreeOut, #6
+ self.properties.dis_error] #7
+
+ obj = context.active_object
+
+ if not self.fcurves:
+ self.fcurves = getFcurveData(obj)
+
+ fcurves_simplify(context, obj, options, self.fcurves)
+
+ #print("-------END-------")
+ return {'FINISHED'}
+
+###########################
+##### Curves OPERATOR #####
+###########################
+class CURVE_OT_simplify(bpy.types.Operator):
+ ''''''
+ bl_idname = "curve.simplify"
+ bl_label = "simplifiy curves"
+ bl_description = "simplify curves"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ ## Properties
+ opModes = [
+ ('distance', 'distance', 'distance'),
+ ('curvature', 'curvature', 'curvature')]
+ mode = EnumProperty(name="Mode",
+ description="choose algorithm to use",
+ items=opModes)
+ SplineTypes = [
+ ('INPUT', 'Input', 'same type as input spline'),
+ ('NURBS', 'Nurbs', 'NURBS'),
+ ('BEZIER', 'Bezier', 'BEZIER'),
+ ('POLY', 'Poly', 'POLY')]
+ output = EnumProperty(name="Output splines",
+ description="Type of splines to output",
+ items=SplineTypes)
+ k_thresh = FloatProperty(name="k",
+ min=0, soft_min=0,
+ default=0, precision=3,
+ description="threshold")
+ pointsNr = IntProperty(name="n",
+ min=5, soft_min=5,
+ max=9, soft_max=9,
+ default=5,
+ description="degree of curve to get averaged curvatures")
+ error = FloatProperty(name="error in Bu",
+ description="maximum error in Blenderunits to allow - distance",
+ min=0, soft_min=0,
+ default=0.0, precision=3)
+ degreeOut = IntProperty(name="degree",
+ min=3, soft_min=3,
+ max=7, soft_max=7,
+ default=5,
+ description="degree of new curve")
+ dis_error = FloatProperty(name="distance error",
+ description="maximum error in Blenderunits to allow - distance",
+ min=0, soft_min=0,
+ default=0.0)
+ keepShort = BoolProperty(name="keep short Splines",
+ description="keep short splines (less then 7 points)",
+ default=True)
+
+ ''' Remove curvature mode as long as it isnn't significantly improved
+
+ def draw(self, context):
+ props = self.properties
+ layout = self.layout
+ col = layout.column()
+ col.label('Mode:')
+ col.prop(props, 'mode', expand=True)
+ if self.properties.mode == 'distance':
+ box = layout.box()
+ box.label(props.mode, icon='ARROW_LEFTRIGHT')
+ box.prop(props, 'error', expand=True)
+ if self.properties.mode == 'curvature':
+ box = layout.box()
+ box.label('degree', icon='SMOOTHCURVE')
+ box.prop(props, 'pointsNr', expand=True)
+ box.label('threshold', icon='PARTICLE_PATH')
+ box.prop(props, 'k_thresh', expand=True)
+ box.label('distance', icon='ARROW_LEFTRIGHT')
+ box.prop(props, 'dis_error', expand=True)
+ col = layout.column()
+ col.separator()
+ col.prop(props, 'output', text='Output', icon='OUTLINER_OB_CURVE')
+ if props.output == 'NURBS':
+ col.prop(props, 'degreeOut', expand=True)
+ col.prop(props, 'keepShort', expand=True)
+ '''
+
+ def draw(self, context):
+ props = self.properties
+ layout = self.layout
+ col = layout.column()
+ col.prop(props, 'error', expand=True)
+ col.prop(props, 'output', text='Output', icon='OUTLINER_OB_CURVE')
+ if props.output == 'NURBS':
+ col.prop(props, 'degreeOut', expand=True)
+ col.prop(props, 'keepShort', expand=True)
+
+
+ ## Check for curve
+ def poll(self, context):
+ obj = context.active_object
+ return (obj and obj.type == 'CURVE')
+
+ ## execute
+ def execute(self, context):
+ #print("------START------")
+
+ options = [
+ self.properties.mode, #0
+ self.properties.output, #1
+ self.properties.k_thresh, #2
+ self.properties.pointsNr, #3
+ self.properties.error, #4
+ self.properties.degreeOut, #5
+ self.properties.dis_error, #6
+ self.properties.keepShort] #7
+
+
+ bpy.context.user_preferences.edit.global_undo = False
+
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=True)
+ obj = context.active_object
+
+ main(context, obj, options)
+
+ bpy.context.user_preferences.edit.global_undo = True
+
+ #print("-------END-------")
+ return {'FINISHED'}
+
+#################################################
+#### REGISTER ###################################
+#################################################
+def register():
+ bpy.types.register(CURVE_OT_simplify)
+ bpy.types.register(GRAPH_OT_simplify)
+
+def unregister():
+ bpy.types.unregister(CURVE_OT_simplify)
+ bpy.types.unregister(GRAPH_OT_simplify)
+
+if __name__ == "__main__":
+ register()
diff --git a/io_anim_camera.py b/io_anim_camera.py
new file mode 100644
index 00000000..23e59fe8
--- /dev/null
+++ b/io_anim_camera.py
@@ -0,0 +1,159 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+bl_addon_info = {
+ 'name': 'Export: Camera Animation',
+ 'author': 'Campbell Barton',
+ 'version': '0.1',
+ 'blender': (2, 5, 3),
+ 'location': 'File > Export > Camera Animation',
+ 'description': 'Export Cameras & Markers',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/File_I-O/Camera_Animation',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22835&group_id=153&atid=469',
+ 'category': 'Import/Export'}
+
+
+import bpy
+
+
+def writeCameras(context, filepath, frame_start, frame_end, only_selected=False):
+
+ data_attrs = ['lens', 'shift_x', 'shift_y', 'dof_distance', 'clip_start', 'clip_end', 'draw_size']
+ obj_attrs = ['hide_render']
+
+ fw = open(filepath, 'w').write
+
+ scene = bpy.context.scene
+
+ cameras = []
+
+ for obj in scene.objects:
+ if only_selected and not obj.select:
+ continue
+ if obj.type != 'CAMERA':
+ continue
+
+ cameras.append((obj, obj.data))
+
+ frame_range = range(frame_start, frame_end + 1)
+
+ fw("cameras = {}\n")
+ fw("scene = bpy.context.scene\n")
+ fw("frame = scene.frame_current - 1\n")
+ fw("\n")
+
+ for obj, obj_data in cameras:
+ fw("data = bpy.data.cameras.new('%s')\n" % obj.name)
+ for attr in data_attrs:
+ fw("data.%s = %s\n" % (attr, repr(getattr(obj_data, attr))))
+
+ fw("obj = bpy.data.objects.new('%s', data)\n" % obj.name)
+
+ for attr in obj_attrs:
+ fw("obj.%s = %s\n" % (attr, repr(getattr(obj, attr))))
+
+ fw("scene.objects.link(obj)\n")
+ fw("cameras['%s'] = obj\n" % obj.name)
+ fw("\n")
+
+
+ for f in frame_range:
+ scene.set_frame(f)
+ fw("# new frame\n")
+ fw("scene.set_frame(%d + frame)\n" % f)
+
+ for obj, obj_data in cameras:
+ fw("obj = cameras['%s']\n" % obj.name)
+
+ matrix = obj.matrix_world.copy()
+ fw("obj.location = %s\n" % repr(tuple(matrix.translation_part())))
+ fw("obj.scale = %s\n" % repr(tuple(matrix.scale_part())))
+ fw("obj.rotation_euler = %s\n" % repr(tuple(matrix.to_euler())))
+
+ fw("obj.keyframe_insert('location')\n")
+ fw("obj.keyframe_insert('scale')\n")
+ fw("obj.keyframe_insert('rotation_euler')\n")
+
+ # only key the angle
+ fw("data = obj.data\n")
+ fw("data.lens = %s\n" % obj_data.lens)
+ fw("data.keyframe_insert('lens')\n")
+
+ fw("\n")
+
+ # now markers
+ fw("# markers\n")
+ for marker in scene.timeline_markers:
+ fw("marker = scene.timeline_markers.add('%s')\n" % marker.name)
+ fw("marker.frame = %d + frame\n" % marker.frame)
+
+ # will fail if the cameras not selected
+ if marker.camera:
+ fw("marker.camera = cameras.get('%s')\n" % marker.camera.name)
+ fw("\n")
+
+
+from bpy.props import *
+
+
+class CameraExporter(bpy.types.Operator):
+ '''Save a python script which re-creartes cameras and markers elsewhere'''
+ bl_idname = "export_animation.cameras"
+ bl_label = "Export Camera & Markers"
+
+ filepath = StringProperty(name="File Path", description="File path used for importing the RAW file", maxlen=1024, default="")
+
+ frame_start = IntProperty(name="Start Frame",
+ description="Start frame for export",
+ default=1, min=1, max=300000)
+ frame_end = IntProperty(name="End Frame",
+ description="End frame for export",
+ default=250, min=1, max=300000)
+ only_selected = BoolProperty(name="Only Selected",
+ default=True)
+
+ def execute(self, context):
+ writeCameras(context, self.properties.filepath, self.properties.frame_start, self.properties.frame_end, self.properties.only_selected)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.properties.frame_start = context.scene.frame_start
+ self.properties.frame_end = context.scene.frame_end
+
+ wm = context.manager
+ wm.add_fileselect(self)
+ return {'RUNNING_MODAL'}
+
+
+def menu_export(self, context):
+ import os
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".py"
+ self.layout.operator(CameraExporter.bl_idname, text="Cameras & Markers (.py)").filepath = default_path
+
+
+def register():
+ bpy.types.register(CameraExporter)
+ bpy.types.INFO_MT_file_export.append(menu_export)
+
+if __name__ == "__main__":
+ register()
diff --git a/io_export_directx_x.py b/io_export_directx_x.py
new file mode 100644
index 00000000..7fb56308
--- /dev/null
+++ b/io_export_directx_x.py
@@ -0,0 +1,1077 @@
+# ***** 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 3 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, see <http://www.gnu.org/licenses/>.
+# All rights reserved.
+# ***** GPL LICENSE BLOCK *****
+
+bl_addon_info = {
+ 'name': 'Export: DirectX Model Format (.x)',
+ 'author': 'Chris Foster (Kira Vakaan)',
+ 'version': '1.4',
+ 'blender': (2, 5, 3),
+ 'location': 'File > Export',
+ 'description': 'Export to the DirectX Model Format (.x)',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/File_I-O/DirectX_Exporter',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22795&group_id=153&atid=469',
+ 'category': 'Import/Export'}
+
+
+import os
+from math import radians
+
+import bpy
+from mathutils import *
+
+#Container for the exporter settings
+class DirectXExporterSettings:
+ def __init__(self,
+ context,
+ FilePath,
+ CoordinateSystem=1,
+ RotateX=True,
+ FlipNormals=False,
+ ApplyModifiers=False,
+ IncludeFrameRate=False,
+ ExportTextures=True,
+ ExportArmatures=False,
+ ExportAnimation=0,
+ ExportMode=1,
+ Verbose=False):
+ self.context = context
+ self.FilePath = FilePath
+ self.CoordinateSystem = int(CoordinateSystem)
+ self.RotateX = RotateX
+ self.FlipNormals = FlipNormals
+ self.ApplyModifiers = ApplyModifiers
+ self.IncludeFrameRate = IncludeFrameRate
+ self.ExportTextures = ExportTextures
+ self.ExportArmatures = ExportArmatures
+ self.ExportAnimation = int(ExportAnimation)
+ self.ExportMode = int(ExportMode)
+ self.Verbose = Verbose
+
+
+def LegalName(Name):
+ NewName = Name.replace(".", "_")
+ NewName = NewName.replace(" ", "_")
+ if NewName[0].isdigit() or NewName in ["ARRAY",
+ "DWORD",
+ "UCHAR",
+ "BINARY",
+ "FLOAT",
+ "ULONGLONG",
+ "BINARY_RESOURCE",
+ "SDWORD",
+ "UNICODE",
+ "CHAR",
+ "STRING",
+ "WORD",
+ "CSTRING",
+ "SWORD",
+ "DOUBLE",
+ "TEMPLATE"]:
+ NewName = "_" + NewName
+ return NewName
+
+
+def ExportDirectX(Config):
+ print("----------\nExporting to {}".format(Config.FilePath))
+ if Config.Verbose:
+ print("Opening File...", end=" ")
+ Config.File = open(Config.FilePath, "w")
+ if Config.Verbose:
+ print("Done")
+
+ if Config.Verbose:
+ print("Generating Object list for export...", end=" ")
+ if Config.ExportMode == 1:
+ Config.ExportList = [Object for Object in Config.context.scene.objects
+ if Object.type in ("ARMATURE", "EMPTY", "MESH")
+ and Object.parent == None]
+ else:
+ ExportList = [Object for Object in Config.context.selected_objects
+ if Object.type in ("ARMATURE", "EMPTY", "MESH")]
+ Config.ExportList = [Object for Object in ExportList
+ if Object.parent not in ExportList]
+ if Config.Verbose:
+ print("Done")
+
+ if Config.Verbose:
+ print("Setting up...", end=" ")
+ Config.SystemMatrix = Matrix()
+ if Config.RotateX:
+ Config.SystemMatrix *= RotationMatrix(radians(-90), 4, "X")
+ if Config.CoordinateSystem == 1:
+ Config.SystemMatrix *= ScaleMatrix(-1, 4, Vector((0, 1, 0)))
+ Config.InverseSystemMatrix = Config.SystemMatrix.copy().invert()
+
+ #Used for animating rotations
+ Config.SystemQuaternion = Quaternion((1,0,0,0))
+ if Config.RotateX:
+ Config.SystemQuaternion = RotationMatrix(radians(-90), 3, "X").to_quat()
+ Config.InverseSystemQuaternion = Config.SystemQuaternion.copy().inverse()
+ Config.FlipZ = -1 if Config.CoordinateSystem == 1 else 1
+
+ if Config.ExportAnimation:
+ CurrentFrame = bpy.context.scene.frame_current
+ bpy.context.scene.frame_current = bpy.context.scene.frame_current
+ if Config.Verbose:
+ print("Done")
+
+ if Config.Verbose:
+ print("Writing Header...", end=" ")
+ WriteHeader(Config)
+ if Config.Verbose:
+ print("Done")
+
+ Config.Whitespace = 0
+ Config.ObjectList = []
+ if Config.Verbose:
+ print("Writing Objects...")
+ WriteObjects(Config, Config.ExportList)
+ if Config.Verbose:
+ print("Done")
+
+ if Config.ExportAnimation:
+ if Config.IncludeFrameRate:
+ if Config.Verbose:
+ print("Writing Frame Rate...", end=" ")
+ Config.File.write("{}AnimTicksPerSecond {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}{};\n".format(" " * Config.Whitespace, int(bpy.context.scene.render.fps / bpy.context.scene.render.fps_base)))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+ if Config.Verbose:
+ print("Writing Animation...")
+ if Config.ExportAnimation==1:
+ WriteKeyedAnimationSet(Config)
+ else:
+ WriteFullAnimationSet(Config)
+ bpy.context.scene.frame_current = CurrentFrame
+ if Config.Verbose:
+ print("Done")
+
+ CloseFile(Config)
+ print("Finished")
+
+
+def GetObjectChildren(Parent):
+ return [Object for Object in Parent.children
+ if Object.type in ("ARMATURE", "EMPTY", "MESH")]
+
+#Returns the vertex count of Mesh, counting each vertex for every face.
+def GetMeshVertexCount(Mesh):
+ VertexCount = 0
+ for Face in Mesh.faces:
+ VertexCount += len(Face.verts)
+ return VertexCount
+
+#Returns the file path of first image texture from Material.
+def GetMaterialTexture(Material):
+ if Material:
+ #Create a list of Textures that have type "IMAGE"
+ ImageTextures = [Material.texture_slots[TextureSlot].texture for TextureSlot in Material.texture_slots.keys() if Material.texture_slots[TextureSlot].texture.type == "IMAGE"]
+ #Refine a new list with only image textures that have a file source
+ ImageFiles = [os.path.basename(Texture.image.filepath) for Texture in ImageTextures if Texture.image.source == "FILE"]
+ if ImageFiles:
+ return ImageFiles[0]
+ return None
+
+
+def WriteHeader(Config):
+ Config.File.write("xof 0303txt 0032\n\n")
+ if Config.ExportArmatures:
+ Config.File.write("template XSkinMeshHeader {\n\
+ <3cf169ce-ff7c-44ab-93c0-f78f62d172e2>\n\
+ WORD nMaxSkinWeightsPerVertex;\n\
+ WORD nMaxSkinWeightsPerFace;\n\
+ WORD nBones;\n\
+}\n\n\
+template SkinWeights {\n\
+ <6f0d123b-bad2-4167-a0d0-80224f25fabb>\n\
+ STRING transformNodeName;\n\
+ DWORD nWeights;\n\
+ array DWORD vertexIndices[nWeights];\n\
+ array float weights[nWeights];\n\
+ Matrix4x4 matrixOffset;\n\
+}\n\n")
+
+
+def WriteObjects(Config, ObjectList):
+ Config.ObjectList += ObjectList
+
+ for Object in ObjectList:
+ if Config.Verbose:
+ print(" Writing Object: {}...".format(Object.name))
+ Config.File.write("{}Frame {} {{\n".format(" " * Config.Whitespace, LegalName(Object.name)))
+
+ Config.Whitespace += 1
+ if Config.Verbose:
+ print(" Writing Local Matrix...", end=" ")
+ WriteLocalMatrix(Config, Object)
+ if Config.Verbose:
+ print("Done")
+
+ if Config.ExportArmatures and Object.type == "ARMATURE":
+ Armature = Object.data
+ ParentList = [Bone for Bone in Armature.bones if Bone.parent == None]
+ if Config.Verbose:
+ print(" Writing Armature Bones...")
+ WriteArmatureBones(Config, Object, ParentList)
+ if Config.Verbose:
+ print(" Done")
+
+ ChildList = GetObjectChildren(Object)
+ if Config.Verbose:
+ print(" Writing Children...")
+ WriteObjects(Config, ChildList)
+ if Config.Verbose:
+ print(" Done Writing Children")
+
+ if Object.type == "MESH":
+ if Config.Verbose:
+ print(" Generating Mesh...", end=" ")
+ if Config.ApplyModifiers:
+ if Config.ExportArmatures:
+ #Create a copy of the object and remove all armature modifiers so an unshaped
+ #mesh can be created from it.
+ Object2 = Object.copy()
+ for Modifier in [Modifier for Modifier in Object2.modifiers if Modifier.type == "ARMATURE"]:
+ Object2.modifiers.remove(Modifier)
+ Mesh = Object2.create_mesh(bpy.context.scene, True, "PREVIEW")
+ else:
+ Mesh = Object.create_mesh(bpy.context.scene, True, "PREVIEW")
+ else:
+ Mesh = Object.create_mesh(bpy.context.scene, False, "PREVIEW")
+ if Config.Verbose:
+ print("Done")
+ print(" Writing Mesh...")
+ WriteMesh(Config, Object, Mesh)
+ if Config.Verbose:
+ print(" Done")
+ if Config.ApplyModifiers and Config.ExportArmatures:
+ bpy.data.objects.remove(Object2)
+ bpy.data.meshes.remove(Mesh)
+
+ Config.Whitespace -= 1
+ Config.File.write("{}}} //End of {}\n".format(" " * Config.Whitespace, LegalName(Object.name)))
+ if Config.Verbose:
+ print(" Done Writing Object: {}".format(Object.name))
+
+
+def WriteLocalMatrix(Config, Object):
+ LocalMatrix = Config.SystemMatrix * Object.matrix_local * Config.InverseSystemMatrix
+
+ Config.File.write("{}FrameTransformMatrix {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, LocalMatrix[0][0], LocalMatrix[0][1], LocalMatrix[0][2], LocalMatrix[0][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, LocalMatrix[1][0], LocalMatrix[1][1], LocalMatrix[1][2], LocalMatrix[1][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, LocalMatrix[2][0], LocalMatrix[2][1], LocalMatrix[2][2], LocalMatrix[2][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, LocalMatrix[3][0], LocalMatrix[3][1], LocalMatrix[3][2], LocalMatrix[3][3]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+
+
+def WriteArmatureBones(Config, Object, ChildList):
+ PoseBones = Object.pose.bones
+ for Bone in ChildList:
+ if Config.Verbose:
+ print(" Writing Bone: {}...".format(Bone.name), end=" ")
+ Config.File.write("{}Frame {} {{\n".format(" " * Config.Whitespace, LegalName(Object.name) + "_" + LegalName(Bone.name)))
+ Config.Whitespace += 1
+
+ PoseBone = PoseBones[Bone.name]
+
+ if Bone.parent:
+ BoneMatrix = (PoseBone.parent.matrix *
+ RotationMatrix(radians(-90), 4, "X")).invert()
+ else:
+ BoneMatrix = Matrix()
+
+ BoneMatrix *= PoseBone.matrix * RotationMatrix(radians(-90), 4, "X")
+ BoneMatrix = Config.SystemMatrix * BoneMatrix * Config.InverseSystemMatrix
+
+ Config.File.write("{}FrameTransformMatrix {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[0][0], BoneMatrix[0][1], BoneMatrix[0][2], BoneMatrix[0][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[1][0], BoneMatrix[1][1], BoneMatrix[1][2], BoneMatrix[1][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[2][0], BoneMatrix[2][1], BoneMatrix[2][2], BoneMatrix[2][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, BoneMatrix[3][0], BoneMatrix[3][1], BoneMatrix[3][2], BoneMatrix[3][3]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+
+ if Config.Verbose:
+ print("Done")
+ WriteArmatureBones(Config, Object, Bone.children)
+ Config.Whitespace -= 1
+
+ Config.File.write("{}}} //End of {}\n".format(" " * Config.Whitespace, LegalName(Object.name) + "_" + LegalName(Bone.name)))
+
+
+def WriteMesh(Config, Object, Mesh):
+ Config.File.write("{}Mesh {{ //{} Mesh\n".format(" " * Config.Whitespace, LegalName(Mesh.name)))
+ Config.Whitespace += 1
+
+ if Config.Verbose:
+ print(" Writing Mesh Vertices...", end=" ")
+ WriteMeshVertices(Config, Mesh)
+ if Config.Verbose:
+ print("Done\n Writing Mesh Normals...", end=" ")
+ WriteMeshNormals(Config, Mesh)
+ if Config.Verbose:
+ print("Done\n Writing Mesh Materials...", end=" ")
+ WriteMeshMaterials(Config, Mesh)
+ if Config.Verbose:
+ print("Done")
+ if Mesh.uv_textures:
+ if Config.Verbose:
+ print(" Writing Mesh UV Coordinates...", end=" ")
+ WriteMeshUVCoordinates(Config, Mesh)
+ if Config.Verbose:
+ print("Done")
+ if Config.ExportArmatures:
+ if Config.Verbose:
+ print(" Writing Mesh Skin Weights...", end=" ")
+ WriteMeshSkinWeights(Config, Object, Mesh)
+ if Config.Verbose:
+ print("Done")
+
+ Config.Whitespace -= 1
+ Config.File.write("{}}} //End of {} Mesh\n".format(" " * Config.Whitespace, LegalName(Mesh.name)))
+
+
+def WriteMeshVertices(Config, Mesh):
+ Index = 0
+ VertexCount = GetMeshVertexCount(Mesh)
+ Config.File.write("{}{};\n".format(" " * Config.Whitespace, VertexCount))
+
+ for Face in Mesh.faces:
+ Vertices = list(Face.verts)
+
+ if Config.CoordinateSystem == 1:
+ Vertices = Vertices[::-1]
+ for Vertex in [Mesh.verts[Vertex] for Vertex in Vertices]:
+ Position = Config.SystemMatrix * Vertex.co
+ Config.File.write("{}{:9f};{:9f};{:9f};".format(" " * Config.Whitespace, Position[0], Position[1], Position[2]))
+ Index += 1
+ if Index == VertexCount:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+
+ Index = 0
+ Config.File.write("{}{};\n".format(" " * Config.Whitespace, len(Mesh.faces)))
+
+ for Face in Mesh.faces:
+ Config.File.write("{}{};".format(" " * Config.Whitespace, len(Face.verts)))
+ for Vertex in Face.verts:
+ Config.File.write("{};".format(Index))
+ Index += 1
+ if Index == VertexCount:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+
+
+def WriteMeshNormals(Config, Mesh):
+ Config.File.write("{}MeshNormals {{ //{} Normals\n".format(" " * Config.Whitespace, LegalName(Mesh.name)))
+ Config.Whitespace += 1
+
+ Index = 0
+ VertexCount = GetMeshVertexCount(Mesh)
+ Config.File.write("{}{};\n".format(" " * Config.Whitespace, VertexCount))
+
+ for Face in Mesh.faces:
+ Vertices = list(Face.verts)
+
+ if Config.CoordinateSystem == 1:
+ Vertices = Vertices[::-1]
+ for Vertex in [Mesh.verts[Vertex] for Vertex in Vertices]:
+ if Face.smooth:
+ Normal = Config.SystemMatrix * Vertex.normal
+ else:
+ Normal = Config.SystemMatrix * Face.normal
+ if Config.FlipNormals:
+ Normal = -Normal
+ Config.File.write("{}{:9f};{:9f};{:9f};".format(" " * Config.Whitespace, Normal[0], Normal[1], Normal[2]))
+ Index += 1
+ if Index == VertexCount:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+
+ Index = 0
+ Config.File.write("{}{};\n".format(" " * Config.Whitespace, len(Mesh.faces)))
+
+ for Face in Mesh.faces:
+ Config.File.write("{}{};".format(" " * Config.Whitespace, len(Face.verts)))
+ for Vertex in Face.verts:
+ Config.File.write("{};".format(Index))
+ Index += 1
+ if Index == VertexCount:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ Config.Whitespace -= 1
+ Config.File.write("{}}} //End of {} Normals\n".format(" " * Config.Whitespace, LegalName(Mesh.name)))
+
+
+def WriteMeshMaterials(Config, Mesh):
+ Config.File.write("{}MeshMaterialList {{ //{} Material List\n".format(" " * Config.Whitespace, LegalName(Mesh.name)))
+ Config.Whitespace += 1
+
+ Materials = Mesh.materials
+ if Materials.keys():
+ MaterialIndexes = {}
+ for Face in Mesh.faces:
+ if Materials[Face.material_index] not in MaterialIndexes:
+ MaterialIndexes[Materials[Face.material_index]] = len(MaterialIndexes)
+
+ FaceCount = len(Mesh.faces)
+ Index = 0
+ Config.File.write("{}{};\n{}{};\n".format(" " * Config.Whitespace, len(MaterialIndexes), " " * Config.Whitespace, FaceCount))
+ for Face in Mesh.faces:
+ Config.File.write("{}{}".format(" " * Config.Whitespace, MaterialIndexes[Materials[Face.material_index]]))
+ Index += 1
+ if Index == FaceCount:
+ Config.File.write(";;\n")
+ else:
+ Config.File.write(",\n")
+
+ Materials = [Item[::-1] for Item in MaterialIndexes.items()]
+ Materials.sort()
+ for Material in Materials:
+ WriteMaterial(Config, Material[1])
+ else:
+ Config.File.write("{}1;\n{}1;\n{}0;;\n".format(" " * Config.Whitespace, " " * Config.Whitespace, " " * Config.Whitespace))
+ WriteMaterial(Config)
+
+ Config.Whitespace -= 1
+ Config.File.write("{}}} //End of {} Material List\n".format(" " * Config.Whitespace, LegalName(Mesh.name)))
+
+
+def WriteMaterial(Config, Material=None):
+ if Material:
+ Config.File.write("{}Material {} {{\n".format(" " * Config.Whitespace, LegalName(Material.name)))
+ Config.Whitespace += 1
+
+ Diffuse = list(Material.diffuse_color)
+ Diffuse.append(Material.alpha)
+ Specularity = Material.specular_intensity
+ Specular = list(Material.specular_color)
+
+ Config.File.write("{}{:9f};{:9f};{:9f};{:9f};;\n".format(" " * Config.Whitespace, Diffuse[0], Diffuse[1], Diffuse[2], Diffuse[3]))
+ Config.File.write("{}{:9f};\n".format(" " * Config.Whitespace, 2 * (1.0 - Specularity)))
+ Config.File.write("{}{:9f};{:9f};{:9f};;\n".format(" " * Config.Whitespace, Specular[0], Specular[1], Specular[2]))
+ else:
+ Config.File.write("{}Material Default_Material {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{} 1.000000; 1.000000; 1.000000; 1.000000;;\n".format(" " * Config.Whitespace))
+ Config.File.write("{} 1.500000;\n".format(" " * Config.Whitespace))
+ Config.File.write("{} 1.000000; 1.000000; 1.000000;;\n".format(" " * Config.Whitespace))
+ Config.File.write("{} 0.000000; 0.000000; 0.000000;;\n".format(" " * Config.Whitespace))
+ if Config.ExportTextures:
+ Texture = GetMaterialTexture(Material)
+ if Texture:
+ Config.File.write("{}TextureFilename {{\"{}\";}}\n".format(" " * Config.Whitespace, Texture))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+
+
+def WriteMeshUVCoordinates(Config, Mesh):
+ Config.File.write("{}MeshTextureCoords {{ //{} UV Coordinates\n".format(" " * Config.Whitespace, LegalName(Mesh.name)))
+ Config.Whitespace += 1
+
+ UVCoordinates = None
+ for UV in Mesh.uv_textures:
+ if UV.active_render:
+ UVCoordinates = UV.data
+ break
+
+ Index = 0
+ VertexCount = GetMeshVertexCount(Mesh)
+ Config.File.write("{}{};\n".format(" " * Config.Whitespace, VertexCount))
+
+ for Face in UVCoordinates:
+ Vertices = []
+ for Vertex in Face.uv:
+ Vertices.append(tuple(Vertex))
+ if Config.CoordinateSystem == 1:
+ Vertices = Vertices[::-1]
+ for Vertex in Vertices:
+ Config.File.write("{}{:9f};{:9f};".format(" " * Config.Whitespace, Vertex[0], 1 - Vertex[1]))
+ Index += 1
+ if Index == VertexCount:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ Config.Whitespace -= 1
+ Config.File.write("{}}} //End of {} UV Coordinates\n".format(" " * Config.Whitespace, LegalName(Mesh.name)))
+
+
+def WriteMeshSkinWeights(Config, Object, Mesh):
+ ArmatureList = [Modifier for Modifier in Object.modifiers if Modifier.type == "ARMATURE"]
+ if ArmatureList:
+ ArmatureObject = ArmatureList[0].object
+ ArmatureBones = ArmatureObject.data.bones
+
+ PoseBones = ArmatureObject.pose.bones
+
+ MaxInfluences = 0
+ UsedBones = set()
+ #Maps bones to a list of vertices they affect
+ VertexGroups = {}
+
+ for Vertex in Mesh.verts:
+ #BoneInfluences contains the bones of the armature that affect the current vertex
+ BoneInfluences = [PoseBones[Object.vertex_groups[Group.group].name] for Group in Vertex.groups if Object.vertex_groups[Group.group].name in PoseBones]
+ if len(BoneInfluences) > MaxInfluences:
+ MaxInfluences = len(BoneInfluences)
+ for Bone in BoneInfluences:
+ UsedBones.add(Bone)
+ if Bone not in VertexGroups:
+ VertexGroups[Bone] = [Vertex]
+ else:
+ VertexGroups[Bone].append(Vertex)
+ BoneCount = len(UsedBones)
+
+ Config.File.write("{}XSkinMeshHeader {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}{};\n{}{};\n{}{};\n".format(" " * Config.Whitespace, MaxInfluences, " " * Config.Whitespace, MaxInfluences * 3, " " * Config.Whitespace, BoneCount))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+
+ for Bone in UsedBones:
+ VertexCount = 0
+ VertexIndexes = [Vertex.index for Vertex in VertexGroups[Bone]]
+ for Face in Mesh.faces:
+ for Vertex in Face.verts:
+ if Vertex in VertexIndexes:
+ VertexCount += 1
+
+ Config.File.write("{}SkinWeights {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}\"{}\";\n{}{};\n".format(" " * Config.Whitespace, LegalName(ArmatureObject.name) + "_" + LegalName(Bone.name), " " * Config.Whitespace, VertexCount))
+
+ VertexWeights = []
+ Index = 0
+ WrittenIndexes = 0
+ for Face in Mesh.faces:
+ FaceVertices = list(Face.verts)
+ if Config.CoordinateSystem == 1:
+ FaceVertices = FaceVertices[::-1]
+ for Vertex in FaceVertices:
+ if Vertex in VertexIndexes:
+ Config.File.write("{}{}".format(" " * Config.Whitespace, Index))
+
+ GroupIndexes = {Object.vertex_groups[Group.group].name: Index for Index, Group in enumerate(Mesh.verts[Vertex].groups) if Object.vertex_groups[Group.group].name in PoseBones}
+
+ WeightTotal = 0.0
+ for Weight in [Group.weight for Group in Mesh.verts[Vertex].groups if Object.vertex_groups[Group.group].name in PoseBones]:
+ WeightTotal += Weight
+
+ if WeightTotal:
+ VertexWeights.append(Mesh.verts[Vertex].groups[GroupIndexes[Bone.name]].weight / WeightTotal)
+ else:
+ VertexWeights.append(0.0)
+
+ WrittenIndexes += 1
+ if WrittenIndexes == VertexCount:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ Index += 1
+
+ for Index, Weight in enumerate(VertexWeights):
+ Config.File.write("{}{:8f}".format(" " * Config.Whitespace, Weight))
+ if Index == (VertexCount - 1):
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+
+ RestBone = ArmatureBones[Bone.name]
+
+ #BoneMatrix transforms mesh vertices into the space of the bone.
+ #Here are the final transformations in order:
+ # - Object Space to World Space
+ # - World Space to Armature Space
+ # - Armature Space to Bone Space (The bone matrix needs to be rotated 90 degrees to align with Blender's world axes)
+ #This way, when BoneMatrix is transformed by the bone's Frame matrix, the vertices will be in their final world position.
+
+ BoneMatrix = (RestBone.matrix_local * RotationMatrix(radians(-90), 4, "X")).invert()
+ BoneMatrix *= ArmatureObject.matrix_world.copy().invert()
+ BoneMatrix *= Object.matrix_world
+
+ BoneMatrix = Config.SystemMatrix * BoneMatrix * Config.InverseSystemMatrix
+
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[0][0], BoneMatrix[0][1], BoneMatrix[0][2], BoneMatrix[0][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[1][0], BoneMatrix[1][1], BoneMatrix[1][2], BoneMatrix[1][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, BoneMatrix[2][0], BoneMatrix[2][1], BoneMatrix[2][2], BoneMatrix[2][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, BoneMatrix[3][0], BoneMatrix[3][1], BoneMatrix[3][2], BoneMatrix[3][3]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}} //End of {} Skin Weights\n".format(" " * Config.Whitespace, LegalName(ArmatureObject.name) + "_" + LegalName(Bone.name)))
+
+
+def WriteKeyedAnimationSet(Config):
+ Config.File.write("{}AnimationSet {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ for Object in [Object for Object in Config.ObjectList if Object.animation_data]:
+ if Config.Verbose:
+ print(" Writing Animation Data for Object: {}".format(Object.name))
+ Action = Object.animation_data.action
+ if Action:
+ PositionFCurves = [None, None, None]
+ RotationFCurves = [None, None, None]
+ ScaleFCurves = [None, None, None]
+ for FCurve in Action.fcurves:
+ if FCurve.data_path == "location":
+ PositionFCurves[FCurve.array_index] = FCurve
+ elif FCurve.data_path == "rotation_euler":
+ RotationFCurves[FCurve.array_index] = FCurve
+ elif FCurve.data_path == "scale":
+ ScaleFCurves[FCurve.array_index] = FCurve
+ if [FCurve for FCurve in PositionFCurves + RotationFCurves + ScaleFCurves if FCurve]:
+ Config.File.write("{}Animation {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}{{{}}}\n".format(" " * Config.Whitespace, LegalName(Object.name)))
+
+ #Position
+ if Config.Verbose:
+ print(" Writing Position...", end=" ")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(PositionFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ Keyframes.append(Keyframe.co)
+ AllKeyframes.add(int(Keyframe.co[0]))
+ PositionFCurves[Index] = {int(Keyframe): Value for Keyframe, Value in Keyframes}
+ Config.File.write("{}AnimationKey {{ //Position\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ AllKeyframes = list(AllKeyframes)
+ AllKeyframes.sort()
+ if len(AllKeyframes):
+ Config.File.write("{}2;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, len(AllKeyframes)))
+ for Keyframe in AllKeyframes:
+ bpy.context.scene.set_frame(Keyframe)
+ Position = Vector()
+ Position[0] = ((PositionFCurves[0][Keyframe] if Keyframe in PositionFCurves[0] else Object.location[0]) if PositionFCurves[0] else Object.location[0])
+ Position[1] = ((PositionFCurves[1][Keyframe] if Keyframe in PositionFCurves[1] else Object.location[1]) if PositionFCurves[1] else Object.location[1])
+ Position[2] = ((PositionFCurves[2][Keyframe] if Keyframe in PositionFCurves[2] else Object.location[2]) if PositionFCurves[2] else Object.location[2])
+ Position = Config.SystemMatrix * Position
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
+ else:
+ Config.File.write("{}2;\n{}1;\n".format(" " * Config.Whitespace, " " * Config.Whitespace))
+ bpy.context.scene.set_frame(bpy.context.scene.frame_start)
+ Position = Config.SystemMatrix * Object.location
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, ("0;3;").ljust(8), Position[0], Position[1], Position[2]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+
+ #Rotation
+ if Config.Verbose:
+ print(" Writing Rotation...", end=" ")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(RotationFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ Keyframes.append(Keyframe.co)
+ AllKeyframes.add(int(Keyframe.co[0]))
+ RotationFCurves[Index] = {int(Keyframe): Value for Keyframe, Value in Keyframes}
+ Config.File.write("{}AnimationKey {{ //Rotation\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ AllKeyframes = list(AllKeyframes)
+ AllKeyframes.sort()
+ if len(AllKeyframes):
+ Config.File.write("{}0;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, len(AllKeyframes)))
+ for Keyframe in AllKeyframes:
+ bpy.context.scene.set_frame(Keyframe)
+ Rotation = Euler()
+ Rotation[0] = ((RotationFCurves[0][Keyframe] if Keyframe in RotationFCurves[0] else Object.rotation_euler[0]) if RotationFCurves[0] else Object.rotation_euler[0])
+ Rotation[1] = ((RotationFCurves[1][Keyframe] if Keyframe in RotationFCurves[1] else Object.rotation_euler[1]) if RotationFCurves[1] else Object.rotation_euler[1])
+ Rotation[2] = ((RotationFCurves[2][Keyframe] if Keyframe in RotationFCurves[2] else Object.rotation_euler[2]) if RotationFCurves[2] else Object.rotation_euler[2])
+ Rotation = (Config.SystemMatrix * (Rotation.to_matrix().to_4x4()) * Config.InverseSystemMatrix).to_quat()
+ Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";4;").ljust(8), - Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
+ else:
+ Config.File.write("{}0;\n{}1;\n".format(" " * Config.Whitespace, " " * Config.Whitespace))
+ bpy.context.scene.set_frame(bpy.context.scene.frame_start)
+ Rotation = (Config.SystemMatrix * (Object.rotation_euler.to_matrix().to_4x4()) * Config.InverseSystemMatrix).to_quat()
+ Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, ("0;4;").ljust(8), -Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+
+ #Scale
+ if Config.Verbose:
+ print(" Writing Scale...", end=" ")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(ScaleFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ Keyframes.append(Keyframe.co)
+ AllKeyframes.add(int(Keyframe.co[0]))
+ ScaleFCurves[Index] = {int(Keyframe): Value for Keyframe, Value in Keyframes}
+ Config.File.write("{}AnimationKey {{ //Scale\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ AllKeyframes = list(AllKeyframes)
+ AllKeyframes.sort()
+ if len(AllKeyframes):
+ Config.File.write("{}1;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, len(AllKeyframes)))
+ for Keyframe in AllKeyframes:
+ bpy.context.scene.set_frame(Keyframe)
+ Scale = Vector()
+ Scale[0] = ((ScaleFCurves[0][Keyframe] if Keyframe in ScaleFCurves[0] else Object.scale[0]) if ScaleFCurves[0] else Object.scale[0])
+ Scale[1] = ((ScaleFCurves[1][Keyframe] if Keyframe in ScaleFCurves[1] else Object.scale[1]) if ScaleFCurves[1] else Object.scale[1])
+ Scale[2] = ((ScaleFCurves[2][Keyframe] if Keyframe in ScaleFCurves[2] else Object.scale[2]) if ScaleFCurves[2] else Object.scale[2])
+ Scale = Config.SystemMatrix * Scale
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
+ else:
+ Config.File.write("{}1;\n{}1;\n".format(" " * Config.Whitespace, " " * Config.Whitespace))
+ bpy.context.scene.set_frame(bpy.context.scene.frame_start)
+ Scale = Config.SystemMatrix * Object.scale
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, ("0;3;").ljust(8), Scale[0], Scale[1], Scale[2]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ else:
+ if Config.Verbose:
+ print(" Object has no useable animation data.")
+
+ if Config.ExportArmatures and Object.type == "ARMATURE":
+ if Config.Verbose:
+ print(" Writing Armature Bone Animation Data...")
+ PoseBones = Object.pose.bones
+ for Bone in PoseBones:
+ if Config.Verbose:
+ print(" Writing Bone: {}...".format(Bone.name))
+ PositionFCurves = [None, None, None]
+ RotationFCurves = [None, None, None, None]
+ ScaleFCurves = [None, None, None]
+ for FCurve in Action.fcurves:
+ if FCurve.data_path == "pose.bones[\"{}\"].location".format(Bone.name):
+ PositionFCurves[FCurve.array_index] = FCurve
+ elif FCurve.data_path == "pose.bones[\"{}\"].rotation_quaternion".format(Bone.name):
+ RotationFCurves[FCurve.array_index] = FCurve
+ elif FCurve.data_path == "pose.bones[\"{}\"].scale".format(Bone.name):
+ ScaleFCurves[FCurve.array_index] = FCurve
+ if not [FCurve for FCurve in PositionFCurves + RotationFCurves + ScaleFCurves if FCurve]:
+ if Config.Verbose:
+ print(" Bone has no useable animation data.\n Done")
+ continue
+
+ Config.File.write("{}Animation {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}{{{}}}\n".format(" " * Config.Whitespace, LegalName(Object.name) + "_" + LegalName(Bone.name)))
+
+ #Position
+ if Config.Verbose:
+ print(" Writing Position...", end=" ")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(PositionFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ Keyframes.append(Keyframe.co)
+ AllKeyframes.add(int(Keyframe.co[0]))
+ PositionFCurves[Index] = {int(Keyframe): Value for Keyframe, Value in Keyframes}
+ Config.File.write("{}AnimationKey {{ //Position\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ AllKeyframes = list(AllKeyframes)
+ AllKeyframes.sort()
+ if not len(AllKeyframes):
+ AllKeyframes = [bpy.context.scene.frame_start]
+ Config.File.write("{}2;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, len(AllKeyframes)))
+ for Keyframe in AllKeyframes:
+ bpy.context.scene.set_frame(Keyframe)
+
+ if Bone.parent:
+ PoseMatrix = (Bone.parent.matrix * RotationMatrix(radians(-90), 4, "X")).invert()
+ else:
+ PoseMatrix = Matrix()
+ PoseMatrix *= Bone.matrix * RotationMatrix(radians(-90), 4, "X")
+
+ PoseMatrix = Config.SystemMatrix * PoseMatrix * Config.InverseSystemMatrix
+
+ Position = PoseMatrix.translation_part()
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+
+ #Rotation
+ if Config.Verbose:
+ print(" Writing Rotation...", end=" ")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(RotationFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ Keyframes.append(Keyframe.co)
+ AllKeyframes.add(int(Keyframe.co[0]))
+ RotationFCurves[Index] = {int(Keyframe): Value for Keyframe, Value in Keyframes}
+ Config.File.write("{}AnimationKey {{ //Rotation\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ AllKeyframes = list(AllKeyframes)
+ AllKeyframes.sort()
+ if not len(AllKeyframes):
+ AllKeyframes = [bpy.context.scene.frame_start]
+ Config.File.write("{}0;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, len(AllKeyframes)))
+ for Keyframe in AllKeyframes:
+ bpy.context.scene.set_frame(Keyframe)
+
+ if Bone.parent:
+ PoseMatrix = (Bone.parent.matrix * RotationMatrix(radians(-90), 4, "X")).invert()
+ else:
+ PoseMatrix = Matrix()
+ PoseMatrix *= Bone.matrix * RotationMatrix(radians(-90), 4, "X")
+
+ PoseMatrix = Config.SystemMatrix * PoseMatrix * Config.InverseSystemMatrix
+
+ Rotation = PoseMatrix.rotation_part().to_quat()
+ Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";4;").ljust(8), -Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+
+ #Scale
+ if Config.Verbose:
+ print(" Writing Scale...", end=" ")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(ScaleFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ Keyframes.append(Keyframe.co)
+ AllKeyframes.add(int(Keyframe.co[0]))
+ ScaleFCurves[Index] = {int(Keyframe): Value for Keyframe, Value in Keyframes}
+ Config.File.write("{}AnimationKey {{ //Scale\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ AllKeyframes = list(AllKeyframes)
+ AllKeyframes.sort()
+ if not len(AllKeyframes):
+ AllKeyframes = [bpy.context.scene.frame_start]
+ Config.File.write("{}1;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, len(AllKeyframes)))
+ for Keyframe in AllKeyframes:
+ bpy.context.scene.set_frame(Keyframe)
+
+ if Bone.parent:
+ PoseMatrix = (Bone.parent.matrix * RotationMatrix(radians(-90), 4, "X")).invert()
+ else:
+ PoseMatrix = Matrix()
+ PoseMatrix *= Bone.matrix * RotationMatrix(radians(-90), 4, "X")
+
+ PoseMatrix = Config.SystemMatrix * PoseMatrix * Config.InverseSystemMatrix
+
+ Scale = PoseMatrix.scale_part()
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print(" Done") #Done with Armature Bone
+ if Config.Verbose:
+ print(" Done") #Done with Armature Bone data
+ if Config.Verbose:
+ print(" Done") #Done with Object
+
+ Config.Whitespace -= 1
+ Config.File.write("{}}} //End of AnimationSet\n".format(" " * Config.Whitespace))
+
+
+def WriteFullAnimationSet(Config):
+ Config.File.write("{}AnimationSet {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+
+ KeyframeCount = bpy.context.scene.frame_end - bpy.context.scene.frame_start + 1
+
+ for Object in Config.ObjectList:
+ if Config.Verbose:
+ print(" Writing Animation Data for Object: {}".format(Object.name))
+
+ Config.File.write("{}Animation {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}{{{}}}\n".format(" " * Config.Whitespace, LegalName(Object.name)))
+
+ #Position
+ if Config.Verbose:
+ print(" Writing Position...", end=" ")
+ Config.File.write("{}AnimationKey {{ //Position\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}2;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
+ for Frame in range(0, KeyframeCount):
+ bpy.context.scene.set_frame(Frame + bpy.context.scene.frame_start)
+ Position = Config.SystemMatrix * Object.location
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+
+ #Rotation
+ if Config.Verbose:
+ print(" Writing Rotation...", end=" ")
+ Config.File.write("{}AnimationKey {{ //Rotation\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}0;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
+ for Frame in range(0, KeyframeCount):
+ bpy.context.scene.set_frame(Frame + bpy.context.scene.frame_start)
+ Rotation = Config.SystemQuaternion.cross(Object.rotation_euler.to_quat().cross(Config.InverseSystemQuaternion))
+ Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, (str(Frame) + ";4;").ljust(8), Rotation[0], Rotation[1], Rotation[2], Config.FlipZ * Rotation[3]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+
+ #Scale
+ if Config.Verbose:
+ print(" Writing Scale...", end=" ")
+ Config.File.write("{}AnimationKey {{ //Scale\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}1;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
+ for Frame in range(0, KeyframeCount):
+ bpy.context.scene.set_frame(Frame + bpy.context.scene.frame_start)
+ Scale = Config.SystemMatrix * Object.scale
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print("Done")
+
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+
+ if Config.ExportArmatures and Object.type == "ARMATURE":
+ if Config.Verbose:
+ print(" Done") #Done with Armature Bone data
+ if Config.Verbose:
+ print(" Done") #Done with Object
+
+ Config.Whitespace -= 1
+ Config.File.write("{}}} //End of AnimationSet\n".format(" " * Config.Whitespace))
+
+
+def CloseFile(Config):
+ if Config.Verbose:
+ print("Closing File...", end=" ")
+ Config.File.close()
+ if Config.Verbose:
+ print("Done")
+
+CoordinateSystems = []
+CoordinateSystems.append(("1", "Left-Handed", ""))
+CoordinateSystems.append(("2", "Right-Handed", ""))
+
+AnimationModes = []
+AnimationModes.append(("0", "None", ""))
+AnimationModes.append(("1", "Keyframes Only", ""))
+AnimationModes.append(("2", "Full Animation", ""))
+
+ExportModes = []
+ExportModes.append(("1", "All Objects", ""))
+ExportModes.append(("2", "Selected Objects", ""))
+
+from bpy.props import *
+
+
+class DirectXExporter(bpy.types.Operator):
+ """Export to the DirectX model format (.x)"""
+
+ bl_idname = "export.directx"
+ bl_label = "Export DirectX"
+
+ filepath = StringProperty()
+ filename = StringProperty()
+ directory = StringProperty()
+
+ #Coordinate System
+ CoordinateSystem = EnumProperty(name="System", description="Select a coordinate system to export to", items=CoordinateSystems, default="1")
+
+ #General Options
+ RotateX = BoolProperty(name="Rotate X 90 Degrees", description="Rotate the entire scene 90 degrees around the X axis so Y is up.", default=True)
+ FlipNormals = BoolProperty(name="Flip Normals", description="", default=False)
+ ApplyModifiers = BoolProperty(name="Apply Modifiers", description="Apply object modifiers before export.", default=False)
+ IncludeFrameRate = BoolProperty(name="Include Frame Rate", description="Include the AnimTicksPerSecond template which is used by some engines to control animation speed.", default=False)
+ ExportTextures = BoolProperty(name="Export Textures", description="Reference external image files to be used by the model.", default=True)
+ ExportArmatures = BoolProperty(name="Export Armatures", description="Export the bones of any armatures to deform meshes.", default=False)
+ ExportAnimation = EnumProperty(name="Animations", description="Select the type of animations to export. Only object and armature bone animations can be exported. Full Animation exports every frame.", items=AnimationModes, default="0")
+
+ #Export Mode
+ ExportMode = EnumProperty(name="Export", description="Select which objects to export. Only Mesh, Empty, and Armature objects will be exported.", items=ExportModes, default="1")
+
+ Verbose = BoolProperty(name="Verbose", description="Run the exporter in debug mode. Check the console for output.", default=False)
+
+ def execute(self, context):
+ #Append .x if needed
+ FilePath = self.properties.filepath
+ if not FilePath.lower().endswith(".x"):
+ FilePath += ".x"
+
+ Config = DirectXExporterSettings(context,
+ FilePath,
+ CoordinateSystem=self.properties.CoordinateSystem,
+ RotateX=self.properties.RotateX,
+ FlipNormals=self.properties.FlipNormals,
+ ApplyModifiers=self.properties.ApplyModifiers,
+ IncludeFrameRate=self.properties.IncludeFrameRate,
+ ExportTextures=self.properties.ExportTextures,
+ ExportArmatures=self.properties.ExportArmatures,
+ ExportAnimation=self.properties.ExportAnimation,
+ ExportMode=self.properties.ExportMode,
+ Verbose=self.properties.Verbose)
+ ExportDirectX(Config)
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ WindowManager = context.manager
+ WindowManager.add_fileselect(self)
+ return {"RUNNING_MODAL"}
+
+
+def menu_func(self, context):
+ DefaultPath = os.path.splitext(bpy.data.filepath)[0] + ".x"
+ self.layout.operator(DirectXExporter.bl_idname, text="DirectX (.x)").filepath = DefaultPath
+
+
+def register():
+ bpy.types.register(DirectXExporter)
+ bpy.types.INFO_MT_file_export.append(menu_func)
+
+
+def unregister():
+ bpy.types.unregister(DirectXExporter)
+ bpy.types.INFO_MT_file_export.remove(menu_func)
+
+
+if __name__ == "__main__":
+ register() \ No newline at end of file
diff --git a/io_export_unreal_psk_psa.py b/io_export_unreal_psk_psa.py
new file mode 100644
index 00000000..4434ad25
--- /dev/null
+++ b/io_export_unreal_psk_psa.py
@@ -0,0 +1,1603 @@
+ # ***** 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 3 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, see <http://www.gnu.org/licenses/>.
+ # All rights reserved.
+ # ***** GPL LICENSE BLOCK *****
+
+"""
+
+-- Unreal Skeletal Mesh and Animation Export (.psk and .psa) export script v0.0.1 --<br>
+
+- NOTES:
+- This script Exports To Unreal's PSK and PSA file formats for Skeletal Meshes and Animations. <br>
+- This script DOES NOT support vertex animation! These require completely different file formats. <br>
+
+- v0.0.1
+- Initial version
+
+- v0.0.2
+- This version adds support for more than one material index!
+
+[ - Edit by: Darknet
+- v0.0.3 - v0.0.12
+- This will work on UT3 and it is a stable version that work with vehicle for testing.
+- Main Bone fix no dummy needed to be there.
+- Just bone issues position, rotation, and offset for psk.
+- The armature bone position, rotation, and the offset of the bone is fix. It was to deal with skeleton mesh export for psk.
+- Animation is fix for position, offset, rotation bone support one rotation direction when armature build.
+- It will convert your mesh into triangular when exporting to psk file.
+- Did not work with psa export yet.
+
+- v0.0.13
+- The animatoin will support different bone rotations when export the animation.
+
+- v0.0.14
+- Fixed Action set keys frames when there is no pose keys and it will ignore it.
+
+- v0.0.15
+- Fixed multiple objects when exporting to psk. Select one mesh to export to psk.
+- ]
+
+- v0.1.1
+- Blender 2.50 svn (Support)
+
+Credit to:
+- export_cal3d.py (Position of the Bones Format)
+- blender2md5.py (Animation Translation Format)
+- export_obj.py (Blender 2.5/Pyhton 3.x Format)
+
+- freenode #blendercoder -> user -> ideasman42
+
+- Give Credit to those who work on this script.
+
+- http://sinsoft.com
+"""
+
+
+bl_addon_info = {
+ 'name': 'Export Skeleletal Mesh/Animation Data',
+ 'author': 'Darknet/Optimus_P-Fat/Active_Trash/Sinsoft',
+ 'version': '2.0',
+ 'blender': (2, 5, 3),
+ 'location': 'File > Export > Skeletal Mesh/Animation Data (.psk/.psa)',
+ 'description': 'Export Unreal Engine (.psk)',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/File_I-O/Unreal_psk_psa',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21366&group_id=153&atid=469',
+ 'category': 'Import/Export'}
+
+
+import os
+import time
+import datetime
+import bpy
+import mathutils
+import operator
+
+from struct import pack, calcsize
+
+MENUPANELBOOL = True
+
+# REFERENCE MATERIAL JUST IN CASE:
+#
+# U = x / sqrt(x^2 + y^2 + z^2)
+# V = y / sqrt(x^2 + y^2 + z^2)
+#
+# Triangles specifed counter clockwise for front face
+#
+#defines for sizeofs
+SIZE_FQUAT = 16
+SIZE_FVECTOR = 12
+SIZE_VJOINTPOS = 44
+SIZE_ANIMINFOBINARY = 168
+SIZE_VCHUNKHEADER = 32
+SIZE_VMATERIAL = 88
+SIZE_VBONE = 120
+SIZE_FNAMEDBONEBINARY = 120
+SIZE_VRAWBONEINFLUENCE = 12
+SIZE_VQUATANIMKEY = 32
+SIZE_VVERTEX = 16
+SIZE_VPOINT = 12
+SIZE_VTRIANGLE = 12
+
+########################################################################
+# Generic Object->Integer mapping
+# the object must be usable as a dictionary key
+class ObjMap:
+ def __init__(self):
+ self.dict = {}
+ self.next = 0
+ def get(self, obj):
+ if obj in self.dict:
+ return self.dict[obj]
+ else:
+ id = self.next
+ self.next = self.next + 1
+ self.dict[obj] = id
+ return id
+
+ def items(self):
+ getval = operator.itemgetter(0)
+ getkey = operator.itemgetter(1)
+ return map(getval, sorted(self.dict.items(), key=getkey))
+
+########################################################################
+# RG - UNREAL DATA STRUCTS - CONVERTED FROM C STRUCTS GIVEN ON UDN SITE
+# provided here: http://udn.epicgames.com/Two/BinaryFormatSpecifications.html
+# updated UDK (Unreal Engine 3): http://udn.epicgames.com/Three/BinaryFormatSpecifications.html
+class FQuat:
+ def __init__(self):
+ self.X = 0.0
+ self.Y = 0.0
+ self.Z = 0.0
+ self.W = 1.0
+
+ def dump(self):
+ data = pack('ffff', self.X, self.Y, self.Z, self.W)
+ return data
+
+ def __cmp__(self, other):
+ return cmp(self.X, other.X) \
+ or cmp(self.Y, other.Y) \
+ or cmp(self.Z, other.Z) \
+ or cmp(self.W, other.W)
+
+ def __hash__(self):
+ return hash(self.X) ^ hash(self.Y) ^ hash(self.Z) ^ hash(self.W)
+
+ def __str__(self):
+ return "[%f,%f,%f,%f](FQuat)" % (self.X, self.Y, self.Z, self.W)
+
+class FVector(object):
+ def __init__(self, X=0.0, Y=0.0, Z=0.0):
+ self.X = X
+ self.Y = Y
+ self.Z = Z
+
+ def dump(self):
+ data = pack('fff', self.X, self.Y, self.Z)
+ return data
+
+ def __cmp__(self, other):
+ return cmp(self.X, other.X) \
+ or cmp(self.Y, other.Y) \
+ or cmp(self.Z, other.Z)
+
+ def _key(self):
+ return (type(self).__name__, self.X, self.Y, self.Z)
+
+ def __hash__(self):
+ return hash(self._key())
+
+ def __eq__(self, other):
+ if not hasattr(other, '_key'):
+ return False
+ return self._key() == other._key()
+
+ def dot(self, other):
+ return self.X * other.X + self.Y * other.Y + self.Z * other.Z
+
+ def cross(self, other):
+ return FVector(self.Y * other.Z - self.Z * other.Y,
+ self.Z * other.X - self.X * other.Z,
+ self.X * other.Y - self.Y * other.X)
+
+ def sub(self, other):
+ return FVector(self.X - other.X,
+ self.Y - other.Y,
+ self.Z - other.Z)
+
+class VJointPos:
+ def __init__(self):
+ self.Orientation = FQuat()
+ self.Position = FVector()
+ self.Length = 0.0
+ self.XSize = 0.0
+ self.YSize = 0.0
+ self.ZSize = 0.0
+
+ def dump(self):
+ data = self.Orientation.dump() + self.Position.dump() + pack('4f', self.Length, self.XSize, self.YSize, self.ZSize)
+ return data
+
+class AnimInfoBinary:
+ def __init__(self):
+ self.Name = "" # length=64
+ self.Group = "" # length=64
+ self.TotalBones = 0
+ self.RootInclude = 0
+ self.KeyCompressionStyle = 0
+ self.KeyQuotum = 0
+ self.KeyPrediction = 0.0
+ self.TrackTime = 0.0
+ self.AnimRate = 0.0
+ self.StartBone = 0
+ self.FirstRawFrame = 0
+ self.NumRawFrames = 0
+
+ def dump(self):
+ data = pack('64s64siiiifffiii', self.Name, self.Group, self.TotalBones, self.RootInclude, self.KeyCompressionStyle, self.KeyQuotum, self.KeyPrediction, self.TrackTime, self.AnimRate, self.StartBone, self.FirstRawFrame, self.NumRawFrames)
+ return data
+
+class VChunkHeader:
+ def __init__(self, name, type_size):
+ self.ChunkID = name # length=20
+ self.TypeFlag = 1999801 # special value
+ self.DataSize = type_size
+ self.DataCount = 0
+
+ def dump(self):
+ data = pack('20siii', self.ChunkID, self.TypeFlag, self.DataSize, self.DataCount)
+ return data
+
+class VMaterial:
+ def __init__(self):
+ self.MaterialName = "" # length=64
+ self.TextureIndex = 0
+ self.PolyFlags = 0 # DWORD
+ self.AuxMaterial = 0
+ self.AuxFlags = 0 # DWORD
+ self.LodBias = 0
+ self.LodStyle = 0
+
+ def dump(self):
+ data = pack('64siLiLii', self.MaterialName, self.TextureIndex, self.PolyFlags, self.AuxMaterial, self.AuxFlags, self.LodBias, self.LodStyle)
+ return data
+
+class VBone:
+ def __init__(self):
+ self.Name = "" # length = 64
+ self.Flags = 0 # DWORD
+ self.NumChildren = 0
+ self.ParentIndex = 0
+ self.BonePos = VJointPos()
+
+ def dump(self):
+ data = pack('64sLii', self.Name, self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
+ return data
+
+#same as above - whatever - this is how Epic does it...
+class FNamedBoneBinary:
+ def __init__(self):
+ self.Name = "" # length = 64
+ self.Flags = 0 # DWORD
+ self.NumChildren = 0
+ self.ParentIndex = 0
+ self.BonePos = VJointPos()
+
+ self.IsRealBone = 0 # this is set to 1 when the bone is actually a bone in the mesh and not a dummy
+
+ def dump(self):
+ data = pack('64sLii', self.Name, self.Flags, self.NumChildren, self.ParentIndex) + self.BonePos.dump()
+ return data
+
+class VRawBoneInfluence:
+ def __init__(self):
+ self.Weight = 0.0
+ self.PointIndex = 0
+ self.BoneIndex = 0
+
+ def dump(self):
+ data = pack('fii', self.Weight, self.PointIndex, self.BoneIndex)
+ return data
+
+class VQuatAnimKey:
+ def __init__(self):
+ self.Position = FVector()
+ self.Orientation = FQuat()
+ self.Time = 0.0
+
+ def dump(self):
+ data = self.Position.dump() + self.Orientation.dump() + pack('f', self.Time)
+ return data
+
+class VVertex(object):
+ def __init__(self):
+ self.PointIndex = 0 # WORD
+ self.U = 0.0
+ self.V = 0.0
+ self.MatIndex = 0 #BYTE
+ self.Reserved = 0 #BYTE
+
+ def dump(self):
+ data = pack('HHffBBH', self.PointIndex, 0, self.U, self.V, self.MatIndex, self.Reserved, 0)
+ return data
+
+ def __cmp__(self, other):
+ return cmp(self.PointIndex, other.PointIndex) \
+ or cmp(self.U, other.U) \
+ or cmp(self.V, other.V) \
+ or cmp(self.MatIndex, other.MatIndex) \
+ or cmp(self.Reserved, other.Reserved)
+
+ def _key(self):
+ return (type(self).__name__,self.PointIndex, self.U, self.V,self.MatIndex,self.Reserved)
+
+ def __hash__(self):
+ return hash(self._key())
+
+ def __eq__(self, other):
+ if not hasattr(other, '_key'):
+ return False
+ return self._key() == other._key()
+
+class VPoint(object):
+ def __init__(self):
+ self.Point = FVector()
+
+ def dump(self):
+ return self.Point.dump()
+
+ def __cmp__(self, other):
+ return cmp(self.Point, other.Point)
+
+ def _key(self):
+ return (type(self).__name__, self.Point)
+
+ def __hash__(self):
+ return hash(self._key())
+
+ def __eq__(self, other):
+ if not hasattr(other, '_key'):
+ return False
+ return self._key() == other._key()
+
+class VTriangle:
+ def __init__(self):
+ self.WedgeIndex0 = 0 # WORD
+ self.WedgeIndex1 = 0 # WORD
+ self.WedgeIndex2 = 0 # WORD
+ self.MatIndex = 0 # BYTE
+ self.AuxMatIndex = 0 # BYTE
+ self.SmoothingGroups = 0 # DWORD
+
+ def dump(self):
+ data = pack('HHHBBL', self.WedgeIndex0, self.WedgeIndex1, self.WedgeIndex2, self.MatIndex, self.AuxMatIndex, self.SmoothingGroups)
+ return data
+
+# END UNREAL DATA STRUCTS
+########################################################################
+
+########################################################################
+#RG - helper class to handle the normal way the UT files are stored
+#as sections consisting of a header and then a list of data structures
+class FileSection:
+ def __init__(self, name, type_size):
+ self.Header = VChunkHeader(name, type_size)
+ self.Data = [] # list of datatypes
+
+ def dump(self):
+ data = self.Header.dump()
+ for i in range(len(self.Data)):
+ data = data + self.Data[i].dump()
+ return data
+
+ def UpdateHeader(self):
+ self.Header.DataCount = len(self.Data)
+
+class PSKFile:
+ def __init__(self):
+ self.GeneralHeader = VChunkHeader("ACTRHEAD", 0)
+ self.Points = FileSection("PNTS0000", SIZE_VPOINT) #VPoint
+ self.Wedges = FileSection("VTXW0000", SIZE_VVERTEX) #VVertex
+ self.Faces = FileSection("FACE0000", SIZE_VTRIANGLE) #VTriangle
+ self.Materials = FileSection("MATT0000", SIZE_VMATERIAL) #VMaterial
+ self.Bones = FileSection("REFSKELT", SIZE_VBONE) #VBone
+ self.Influences = FileSection("RAWWEIGHTS", SIZE_VRAWBONEINFLUENCE) #VRawBoneInfluence
+
+ #RG - this mapping is not dumped, but is used internally to store the new point indices
+ # for vertex groups calculated during the mesh dump, so they can be used again
+ # to dump bone influences during the armature dump
+ #
+ # the key in this dictionary is the VertexGroup/Bone Name, and the value
+ # is a list of tuples containing the new point index and the weight, in that order
+ #
+ # Layout:
+ # { groupname : [ (index, weight), ... ], ... }
+ #
+ # example:
+ # { 'MyVertexGroup' : [ (0, 1.0), (5, 1.0), (3, 0.5) ] , 'OtherGroup' : [(2, 1.0)] }
+
+ self.VertexGroups = {}
+
+ def AddPoint(self, p):
+ #print ('AddPoint')
+ self.Points.Data.append(p)
+
+ def AddWedge(self, w):
+ #print ('AddWedge')
+ self.Wedges.Data.append(w)
+
+ def AddFace(self, f):
+ #print ('AddFace')
+ self.Faces.Data.append(f)
+
+ def AddMaterial(self, m):
+ #print ('AddMaterial')
+ self.Materials.Data.append(m)
+
+ def AddBone(self, b):
+ #print ('AddBone [%s]: Position: (x=%f, y=%f, z=%f) Rotation=(%f,%f,%f,%f)' % (b.Name, b.BonePos.Position.X, b.BonePos.Position.Y, b.BonePos.Position.Z, b.BonePos.Orientation.X,b.BonePos.Orientation.Y,b.BonePos.Orientation.Z,b.BonePos.Orientation.W))
+ self.Bones.Data.append(b)
+
+ def AddInfluence(self, i):
+ #print ('AddInfluence')
+ self.Influences.Data.append(i)
+
+ def UpdateHeaders(self):
+ self.Points.UpdateHeader()
+ self.Wedges.UpdateHeader()
+ self.Faces.UpdateHeader()
+ self.Materials.UpdateHeader()
+ self.Bones.UpdateHeader()
+ self.Influences.UpdateHeader()
+
+ def dump(self):
+ self.UpdateHeaders()
+ data = self.GeneralHeader.dump() + self.Points.dump() + self.Wedges.dump() + self.Faces.dump() + self.Materials.dump() + self.Bones.dump() + self.Influences.dump()
+ return data
+
+ def GetMatByIndex(self, mat_index):
+ if mat_index >= 0 and len(self.Materials.Data) > mat_index:
+ return self.Materials.Data[mat_index]
+ else:
+ m = VMaterial()
+ m.MaterialName = "Mat%i" % mat_index
+ self.AddMaterial(m)
+ return m
+
+ def PrintOut(self):
+ print ("--- PSK FILE EXPORTED ---")
+ print ('point count: %i' % len(self.Points.Data))
+ print ('wedge count: %i' % len(self.Wedges.Data))
+ print ('face count: %i' % len(self.Faces.Data))
+ print ('material count: %i' % len(self.Materials.Data))
+ print ('bone count: %i' % len(self.Bones.Data))
+ print ('inlfuence count: %i' % len(self.Influences.Data))
+ print ('-------------------------')
+
+# PSA FILE NOTES FROM UDN:
+#
+# The raw key array holds all the keys for all the bones in all the specified sequences,
+# organized as follows:
+# For each AnimInfoBinary's sequence there are [Number of bones] times [Number of frames keys]
+# in the VQuatAnimKeys, laid out as tracks of [numframes] keys for each bone in the order of
+# the bones as defined in the array of FnamedBoneBinary in the PSA.
+#
+# Once the data from the PSK (now digested into native skeletal mesh) and PSA (digested into
+# a native animation object containing one or more sequences) are associated together at runtime,
+# bones are linked up by name. Any bone in a skeleton (from the PSK) that finds no partner in
+# the animation sequence (from the PSA) will assume its reference pose stance ( as defined in
+# the offsets & rotations that are in the VBones making up the reference skeleton from the PSK)
+
+class PSAFile:
+ def __init__(self):
+ self.GeneralHeader = VChunkHeader("ANIMHEAD", 0)
+ self.Bones = FileSection("BONENAMES", SIZE_FNAMEDBONEBINARY) #FNamedBoneBinary
+ self.Animations = FileSection("ANIMINFO", SIZE_ANIMINFOBINARY) #AnimInfoBinary
+ self.RawKeys = FileSection("ANIMKEYS", SIZE_VQUATANIMKEY) #VQuatAnimKey
+
+ # this will take the format of key=Bone Name, value = (BoneIndex, Bone Object)
+ # THIS IS NOT DUMPED
+ self.BoneLookup = {}
+
+ def dump(self):
+ data = self.Generalheader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump()
+ return data
+
+ def AddBone(self, b):
+ #LOUD
+ #print "AddBone: " + b.Name
+ self.Bones.Data.append(b)
+
+ def AddAnimation(self, a):
+ #LOUD
+ #print "AddAnimation: %s, TotalBones: %i, AnimRate: %f, NumRawFrames: %i, TrackTime: %f" % (a.Name, a.TotalBones, a.AnimRate, a.NumRawFrames, a.TrackTime)
+ self.Animations.Data.append(a)
+
+ def AddRawKey(self, k):
+ #LOUD
+ #print "AddRawKey [%i]: Time: %f, Quat: x=%f, y=%f, z=%f, w=%f, Position: x=%f, y=%f, z=%f" % (len(self.RawKeys.Data), k.Time, k.Orientation.X, k.Orientation.Y, k.Orientation.Z, k.Orientation.W, k.Position.X, k.Position.Y, k.Position.Z)
+ self.RawKeys.Data.append(k)
+
+ def UpdateHeaders(self):
+ self.Bones.UpdateHeader()
+ self.Animations.UpdateHeader()
+ self.RawKeys.UpdateHeader()
+
+ def GetBoneByIndex(self, bone_index):
+ if bone_index >= 0 and len(self.Bones.Data) > bone_index:
+ return self.Bones.Data[bone_index]
+
+ def IsEmpty(self):
+ return (len(self.Bones.Data) == 0 or len(self.Animations.Data) == 0)
+
+ def StoreBone(self, b):
+ self.BoneLookup[b.Name] = [-1, b]
+
+ def UseBone(self, bone_name):
+ if bone_name in self.BoneLookup:
+ bone_data = self.BoneLookup[bone_name]
+
+ if bone_data[0] == -1:
+ bone_data[0] = len(self.Bones.Data)
+ self.AddBone(bone_data[1])
+ #self.Bones.Data.append(bone_data[1])
+
+ return bone_data[0]
+
+ def GetBoneByName(self, bone_name):
+ if bone_name in self.BoneLookup:
+ bone_data = self.BoneLookup[bone_name]
+ return bone_data[1]
+
+ def GetBoneIndex(self, bone_name):
+ if bone_name in self.BoneLookup:
+ bone_data = self.BoneLookup[bone_name]
+ return bone_data[0]
+
+ def dump(self):
+ self.UpdateHeaders()
+ data = self.GeneralHeader.dump() + self.Bones.dump() + self.Animations.dump() + self.RawKeys.dump()
+ return data
+
+ def PrintOut(self):
+ print ('--- PSA FILE EXPORTED ---')
+ print ('bone count: %i' % len(self.Bones.Data))
+ print ('animation count: %i' % len(self.Animations.Data))
+ print ('rawkey count: %i' % len(self.RawKeys.Data))
+ print ('-------------------------')
+
+####################################
+# helpers to create bone structs
+def make_vbone(name, parent_index, child_count, orientation_quat, position_vect):
+ bone = VBone()
+ bone.Name = name
+ bone.ParentIndex = parent_index
+ bone.NumChildren = child_count
+ bone.BonePos.Orientation = orientation_quat
+ bone.BonePos.Position.X = position_vect.x
+ bone.BonePos.Position.Y = position_vect.y
+ bone.BonePos.Position.Z = position_vect.z
+
+ #these values seem to be ignored?
+ #bone.BonePos.Length = tail.length
+ #bone.BonePos.XSize = tail.x
+ #bone.BonePos.YSize = tail.y
+ #bone.BonePos.ZSize = tail.z
+
+ return bone
+
+def make_namedbonebinary(name, parent_index, child_count, orientation_quat, position_vect, is_real):
+ bone = FNamedBoneBinary()
+ bone.Name = name
+ bone.ParentIndex = parent_index
+ bone.NumChildren = child_count
+ bone.BonePos.Orientation = orientation_quat
+ bone.BonePos.Position.X = position_vect.x
+ bone.BonePos.Position.Y = position_vect.y
+ bone.BonePos.Position.Z = position_vect.z
+ bone.IsRealBone = is_real
+ return bone
+
+##################################################
+#RG - check to make sure face isnt a line
+#The face has to be triangle not a line
+def is_1d_face(blender_face,mesh):
+ #ID Vertex of id point
+ v0 = blender_face.verts[0]
+ v1 = blender_face.verts[1]
+ v2 = blender_face.verts[2]
+
+ return (mesh.verts[v0].co == mesh.verts[v1].co or \
+ mesh.verts[v1].co == mesh.verts[v2].co or \
+ mesh.verts[v2].co == mesh.verts[v0].co)
+ return False
+
+##################################################
+# http://en.wikibooks.org/wiki/Blender_3D:_Blending_Into_Python/Cookbook#Triangulate_NMesh
+
+#blender 2.50 format using the Operators/command convert the mesh to tri mesh
+def triangulateNMesh(object):
+ bneedtri = False
+ scene = bpy.context.scene
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for i in scene.objects: i.select = False #deselect all objects
+ object.select = True
+ scene.objects.active = object #set the mesh object to current
+ bpy.ops.object.mode_set(mode='OBJECT')
+ print("Checking mesh if needs to convert quad to Tri...")
+ for face in object.data.faces:
+ if (len(face.verts) > 3):
+ bneedtri = True
+ break
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ if bneedtri == True:
+ print("Converting quad to tri mesh...")
+ me_da = object.data.copy() #copy data
+ me_ob = object.copy() #copy object
+ #note two copy two types else it will use the current data or mesh
+ me_ob.data = me_da
+ bpy.context.scene.objects.link(me_ob)#link the object to the scene #current object location
+ for i in scene.objects: i.select = False #deselect all objects
+ me_ob.select = True
+ scene.objects.active = me_ob #set the mesh object to current
+ bpy.ops.object.mode_set(mode='EDIT') #Operators
+ bpy.ops.mesh.select_all(action='SELECT')#select all the face/vertex/edge
+ bpy.ops.mesh.quads_convert_to_tris() #Operators
+ bpy.context.scene.update()
+ bpy.ops.object.mode_set(mode='OBJECT') # set it in object
+ bpy.context.scene.unrealtriangulatebool = True
+ print("Triangulate Mesh Done!")
+ else:
+ print("No need to convert tri mesh.")
+ me_ob = object
+ return me_ob
+
+#Blender Bone Index
+class BBone:
+ def __init__(self):
+ self.bone = ""
+ self.index = 0
+bonedata = []
+BBCount = 0
+#deal with mesh bones groups vertex point
+def BoneIndex(bone):
+ global BBCount, bonedata
+ #print("//==============")
+ #print(bone.name , "ID:",BBCount)
+ BB = BBone()
+ BB.bone = bone.name
+ BB.index = BBCount
+ bonedata.append(BB)
+ BBCount += 1
+ for current_child_bone in bone.children:
+ BoneIndex(current_child_bone)
+
+def BoneIndexArmature(blender_armature):
+ global BBCount
+ #print("\n Buildng bone before mesh \n")
+ #objectbone = blender_armature.pose #Armature bone
+ #print(blender_armature)
+ objectbone = blender_armature[0].pose
+ #print(dir(ArmatureData))
+
+ for bone in objectbone.bones:
+ if(bone.parent == None):
+ BoneIndex(bone)
+ #BBCount += 1
+ break
+
+# Actual object parsing functions
+def parse_meshes(blender_meshes, psk_file):
+ #this is use to call the bone name and the index array for group index matches
+ global bonedata
+ #print("BONE DATA",len(bonedata))
+ print ("----- parsing meshes -----")
+ print("Number of Object Meshes:",len(blender_meshes))
+ for current_obj in blender_meshes: #number of mesh that should be one mesh here
+
+ current_obj = triangulateNMesh(current_obj)
+ #print(dir(current_obj))
+ print("Mesh Name:",current_obj.name)
+ current_mesh = current_obj.data
+
+ #if len(current_obj.materials) > 0:
+ # object_mat = current_obj.materials[0]
+ object_material_index = current_obj.active_material_index
+
+ points = ObjMap()
+ wedges = ObjMap()
+
+ discarded_face_count = 0
+ print (" -- Dumping Mesh Faces -- LEN:", len(current_mesh.faces))
+ for current_face in current_mesh.faces:
+ #print ' -- Dumping UVs -- '
+ #print current_face.uv_textures
+
+ if len(current_face.verts) != 3:
+ raise RuntimeError("Non-triangular face (%i)" % len(current_face.verts))
+
+ #No Triangulate Yet
+ # if len(current_face.verts) != 3:
+ # raise RuntimeError("Non-triangular face (%i)" % len(current_face.verts))
+ # #TODO: add two fake faces made of triangles?
+
+ #RG - apparently blender sometimes has problems when you do quad to triangle
+ # conversion, and ends up creating faces that have only TWO points -
+ # one of the points is simply in the vertex list for the face twice.
+ # This is bad, since we can't get a real face normal for a LINE, we need
+ # a plane for this. So, before we add the face to the list of real faces,
+ # ensure that the face is actually a plane, and not a line. If it is not
+ # planar, just discard it and notify the user in the console after we're
+ # done dumping the rest of the faces
+
+ if not is_1d_face(current_face,current_mesh):
+ #print("faces")
+ wedge_list = []
+ vect_list = []
+
+ #get or create the current material
+ m = psk_file.GetMatByIndex(object_material_index)
+
+ face_index = current_face.index
+ has_UV = False
+ faceUV = None
+
+ if len(current_mesh.uv_textures) > 0:
+ has_UV = True
+ #print("face index: ",face_index)
+ #faceUV = current_mesh.active_uv_texture.data[face_index]#UVs for current face
+ #faceUV = current_mesh.active_uv_texture.data[0]#UVs for current face
+ #print(face_index,"<[FACE NUMBER")
+ uv_layer = current_mesh.active_uv_texture
+ faceUV = uv_layer.data[face_index]
+ #print("============================")
+ #size(data) is number of texture faces. Each face has UVs
+ #print("DATA face uv: ",len(faceUV.uv), " >> ",(faceUV.uv[0][0]))
+
+ for i in range(3):
+ vert_index = current_face.verts[i]
+ vert = current_mesh.verts[vert_index]
+ uv = []
+ #assumes 3 UVs Per face (for now).
+ if (has_UV):
+ if len(faceUV.uv) != 3:
+ print ("WARNING: Current face is missing UV coordinates - writing 0,0...")
+ print ("WARNING: Face has more than 3 UVs - writing 0,0...")
+ uv = [0.0, 0.0]
+ else:
+ #uv.append(faceUV.uv[i][0])
+ #uv.append(faceUV.uv[i][1])
+ uv = [faceUV.uv[i][0],faceUV.uv[i][1]] #OR bottom works better # 24 for cube
+ #uv = list(faceUV.uv[i]) #30 just cube
+ else:
+ print ("No UVs?")
+ uv = [0.0, 0.0]
+ #print("UV >",uv)
+ #uv = [0.0, 0.0] #over ride uv that is not fixed
+ #print(uv)
+ #flip V coordinate because UEd requires it and DOESN'T flip it on its own like it
+ #does with the mesh Y coordinates.
+ #this is otherwise known as MAGIC-2
+ uv[1] = 1.0 - uv[1]
+
+ #deal with the min and max value
+ #if value is over the set limit it will null the uv texture
+ if (uv[0] > 1):
+ uv[0] = 1
+ if (uv[0] < 0):
+ uv[0] = 0
+ if (uv[1] > 1):
+ uv[1] = 1
+ if (uv[1] < 0):
+ uv[1] = 0
+
+
+ # RE - Append untransformed vector (for normal calc below)
+ # TODO: convert to Blender.Mathutils
+ vect_list.append(FVector(vert.co.x, vert.co.y, vert.co.z))
+
+ # Transform position for export
+ #vpos = vert.co * object_material_index
+ vpos = vert.co * current_obj.matrix_local
+ # Create the point
+ p = VPoint()
+ p.Point.X = vpos.x
+ p.Point.Y = vpos.y
+ p.Point.Z = vpos.z
+
+ # Create the wedge
+ w = VVertex()
+ w.MatIndex = object_material_index
+ w.PointIndex = points.get(p) # get index from map
+ #Set UV TEXTURE
+ w.U = uv[0]
+ w.V = uv[1]
+ index_wedge = wedges.get(w)
+ wedge_list.append(index_wedge)
+
+ #print results
+ #print 'result PointIndex=%i, U=%f, V=%f, wedge_index=%i' % (
+ # w.PointIndex,
+ # w.U,
+ # w.V,
+ # wedge_index)
+
+ # Determine face vertex order
+ # get normal from blender
+ no = current_face.normal
+
+ # TODO: convert to Blender.Mathutils
+ # convert to FVector
+ norm = FVector(no[0], no[1], no[2])
+
+ # Calculate the normal of the face in blender order
+ tnorm = vect_list[1].sub(vect_list[0]).cross(vect_list[2].sub(vect_list[1]))
+
+ # RE - dot the normal from blender order against the blender normal
+ # this gives the product of the two vectors' lengths along the blender normal axis
+ # all that matters is the sign
+ dot = norm.dot(tnorm)
+
+ # print results
+ #print 'face norm: (%f,%f,%f), tnorm=(%f,%f,%f), dot=%f' % (
+ # norm.X, norm.Y, norm.Z,
+ # tnorm.X, tnorm.Y, tnorm.Z,
+ # dot)
+
+ tri = VTriangle()
+ # RE - magic: if the dot product above > 0, order the vertices 2, 1, 0
+ # if the dot product above < 0, order the vertices 0, 1, 2
+ # if the dot product is 0, then blender's normal is coplanar with the face
+ # and we cannot deduce which side of the face is the outside of the mesh
+ if (dot > 0):
+ (tri.WedgeIndex2, tri.WedgeIndex1, tri.WedgeIndex0) = wedge_list
+ elif (dot < 0):
+ (tri.WedgeIndex0, tri.WedgeIndex1, tri.WedgeIndex2) = wedge_list
+ else:
+ dindex0 = current_face.verts[0];
+ dindex1 = current_face.verts[1];
+ dindex2 = current_face.verts[2];
+ raise RuntimeError("normal vector coplanar with face! points:", current_mesh.verts[dindex0].co, current_mesh.verts[dindex1].co, current_mesh.verts[dindex2].co)
+
+ tri.MatIndex = object_material_index
+ #print(tri)
+ psk_file.AddFace(tri)
+
+ else:
+ discarded_face_count = discarded_face_count + 1
+
+ print (" -- Dumping Mesh Points -- LEN:",len(points.dict))
+ for point in points.items():
+ psk_file.AddPoint(point)
+ print (" -- Dumping Mesh Wedge -- LEN:",len(wedges.dict))
+ for wedge in wedges.items():
+ psk_file.AddWedge(wedge)
+
+ #RG - if we happend upon any non-planar faces above that we've discarded,
+ # just let the user know we discarded them here in case they want
+ # to investigate
+
+ if discarded_face_count > 0:
+ print ("INFO: Discarded %i non-planar faces." % (discarded_face_count))
+
+ #RG - walk through the vertex groups and find the indexes into the PSK points array
+ #for them, then store that index and the weight as a tuple in a new list of
+ #verts for the group that we can look up later by bone name, since Blender matches
+ #verts to bones for influences by having the VertexGroup named the same thing as
+ #the bone
+
+ #vertex group.
+ for bonegroup in bonedata:
+ #print("bone gourp build:",bonegroup.bone)
+ vert_list = []
+ for current_vert in current_mesh.verts:
+ #print("INDEX V:",current_vert.index)
+ vert_index = current_vert.index
+ for vgroup in current_vert.groups:#vertex groupd id
+ vert_weight = vgroup.weight
+ if(bonegroup.index == vgroup.group):
+ p = VPoint()
+ vpos = current_vert.co * current_obj.matrix_local
+ p.Point.X = vpos.x
+ p.Point.Y = vpos.y
+ p.Point.Z = vpos.z
+ #print(current_vert.co)
+ point_index = points.get(p) #point index
+ v_item = (point_index, vert_weight)
+ vert_list.append(v_item)
+ #bone name, [point id and wieght]
+ #print("Add Vertex Group:",bonegroup.bone, " No. Points:",len(vert_list))
+ psk_file.VertexGroups[bonegroup.bone] = vert_list
+
+ #unrealtriangulatebool #this will remove the mesh from the scene
+ if (bpy.context.scene.unrealtriangulatebool == True):
+ print("Remove tmp Mesh [ " ,current_obj.name, " ] from scene >" ,(bpy.context.scene.unrealtriangulatebool ))
+ bpy.ops.object.mode_set(mode='OBJECT') # set it in object
+ bpy.context.scene.objects.unlink(current_obj)
+
+def make_fquat(bquat):
+ quat = FQuat()
+
+ #flip handedness for UT = set x,y,z to negative (rotate in other direction)
+ quat.X = -bquat.x
+ quat.Y = -bquat.y
+ quat.Z = -bquat.z
+
+ quat.W = bquat.w
+ return quat
+
+def make_fquat_default(bquat):
+ quat = FQuat()
+
+ quat.X = bquat.x
+ quat.Y = bquat.y
+ quat.Z = bquat.z
+
+ quat.W = bquat.w
+ return quat
+
+# =================================================================================================
+# TODO: remove this 1am hack
+nbone = 0
+def parse_bone(blender_bone, psk_file, psa_file, parent_id, is_root_bone, parent_matrix, parent_root):
+ global nbone # look it's evil!
+ #print '-------------------- Dumping Bone ---------------------- '
+
+ #If bone does not have parent that mean it the root bone
+ if blender_bone.parent == None:
+ parent_root = blender_bone
+
+
+ child_count = len(blender_bone.children)
+ #child of parent
+ child_parent = blender_bone.parent
+
+ if child_parent != None:
+ print ("--Bone Name:",blender_bone.name ," parent:" , blender_bone.parent.name, "ID:", nbone)
+ else:
+ print ("--Bone Name:",blender_bone.name ," parent: None" , "ID:", nbone)
+
+ if child_parent != None:
+ quat_root = blender_bone.matrix
+ quat = make_fquat(quat_root.to_quat())
+
+ quat_parent = child_parent.matrix.to_quat().inverse()
+ parent_head = child_parent.head * quat_parent
+ parent_tail = child_parent.tail * quat_parent
+
+ set_position = (parent_tail - parent_head) + blender_bone.head
+ else:
+ # ROOT BONE
+ #This for root
+ set_position = blender_bone.head * parent_matrix #ARMATURE OBJECT Locction
+ rot_mat = blender_bone.matrix * parent_matrix.rotation_part() #ARMATURE OBJECT Rotation
+ #print(dir(rot_mat))
+
+ quat = make_fquat_default(rot_mat.to_quat())
+
+ print ("[[======= FINAL POSITION:", set_position)
+ final_parent_id = parent_id
+
+ #RG/RE -
+ #if we are not seperated by a small distance, create a dummy bone for the displacement
+ #this is only needed for root bones, since UT assumes a connected skeleton, and from here
+ #down the chain we just use "tail" as an endpoint
+ #if(head.length > 0.001 and is_root_bone == 1):
+ if(0):
+ pb = make_vbone("dummy_" + blender_bone.name, parent_id, 1, FQuat(), tail)
+ psk_file.AddBone(pb)
+ pbb = make_namedbonebinary("dummy_" + blender_bone.name, parent_id, 1, FQuat(), tail, 0)
+ psa_file.StoreBone(pbb)
+ final_parent_id = nbone
+ nbone = nbone + 1
+ #tail = tail-head
+
+ my_id = nbone
+
+ pb = make_vbone(blender_bone.name, final_parent_id, child_count, quat, set_position)
+ psk_file.AddBone(pb)
+ pbb = make_namedbonebinary(blender_bone.name, final_parent_id, child_count, quat, set_position, 1)
+ psa_file.StoreBone(pbb)
+
+ nbone = nbone + 1
+
+ #RG - dump influences for this bone - use the data we collected in the mesh dump phase
+ # to map our bones to vertex groups
+ #print("///////////////////////")
+ #print("set influence")
+ if blender_bone.name in psk_file.VertexGroups:
+ vertex_list = psk_file.VertexGroups[blender_bone.name]
+ #print("vertex list:", len(vertex_list), " of >" ,blender_bone.name )
+ for vertex_data in vertex_list:
+ #print("set influence vettex")
+ point_index = vertex_data[0]
+ vertex_weight = vertex_data[1]
+ influence = VRawBoneInfluence()
+ influence.Weight = vertex_weight
+ #influence.BoneIndex = my_id
+ influence.BoneIndex = my_id
+ influence.PointIndex = point_index
+ #print(influence)
+ #print ('Adding Bone Influence for [%s] = Point Index=%i, Weight=%f' % (blender_bone.name, point_index, vertex_weight))
+ #print("adding influence")
+ psk_file.AddInfluence(influence)
+
+ #blender_bone.matrix_local
+ #recursively dump child bones
+ mainparent = parent_matrix
+ #if len(blender_bone.children) > 0:
+ for current_child_bone in blender_bone.children:
+ parse_bone(current_child_bone, psk_file, psa_file, my_id, 0, mainparent, parent_root)
+
+def parse_armature(blender_armature, psk_file, psa_file):
+ print ("----- parsing armature -----")
+ print ('blender_armature length: %i' % (len(blender_armature)))
+
+ #magic 0 sized root bone for UT - this is where all armature dummy bones will attach
+ #dont increment nbone here because we initialize it to 1 (hackity hackity hack)
+
+ #count top level bones first. NOT EFFICIENT.
+ child_count = 0
+ for current_obj in blender_armature:
+ current_armature = current_obj.data
+ bones = [x for x in current_armature.bones if not x.parent == None]
+ child_count += len(bones)
+
+ for current_obj in blender_armature:
+ print ("Current Armature Name: " + current_obj.name)
+ current_armature = current_obj.data
+ #armature_id = make_armature_bone(current_obj, psk_file, psa_file)
+
+ #we dont want children here - only the top level bones of the armature itself
+ #we will recursively dump the child bones as we dump these bones
+ """
+ bones = [x for x in current_armature.bones if not x.parent == None]
+ #will ingore this part of the ocde
+ """
+ for current_bone in current_armature.bones: #list the bone. #note this will list all the bones.
+ if(current_bone.parent == None):
+ parse_bone(current_bone, psk_file, psa_file, 0, 0, current_obj.matrix_local, None)
+ break
+
+# get blender objects by type
+def get_blender_objects(objects, intype):
+ return [x for x in objects if x.type == intype]
+
+#strips current extension (if any) from filename and replaces it with extension passed in
+def make_filename_ext(filename, extension):
+ new_filename = ''
+ extension_index = filename.find('.')
+
+ if extension_index == -1:
+ new_filename = filename + extension
+ else:
+ new_filename = filename[0:extension_index] + extension
+
+ return new_filename
+
+# returns the quaternion Grassman product a*b
+# this is the same as the rotation a(b(x))
+# (ie. the same as B*A if A and B are matrices representing
+# the rotations described by quaternions a and b)
+def grassman(a, b):
+ return mathutils.Quaternion(
+ a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z,
+ a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y,
+ a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x,
+ a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w)
+
+def parse_animation(blender_scene, blender_armatures, psa_file):
+ #to do list:
+ #need to list the action sets
+ #need to check if there animation
+ #need to check if animation is has one frame then exit it
+ print ('\n----- parsing animation -----')
+ ##print(dir(blender_scene))
+
+ #print(dir(blender_armatures))
+
+ render_data = blender_scene.render
+ bHaveAction = True
+
+ anim_rate = render_data.fps
+
+ #print("dir:",dir(blender_scene))
+ #print(dir(bpy.data.actions))
+ #print("dir:",dir(bpy.data.actions[0]))
+
+
+ print("==== Blender Settings ====")
+ print ('Scene: %s Start Frame: %i, End Frame: %i' % (blender_scene.name, blender_scene.frame_start, blender_scene.frame_end))
+ print ('Frames Per Sec: %i' % anim_rate)
+ print ("Default FPS: 24" )
+
+ cur_frame_index = 0
+
+ #print(dir(bpy.data.actions))
+ #print(dir(bpy.context.scene.set))
+
+ #list of armature objects
+ for arm in blender_armatures:
+ #check if there animation data from armature or something
+ #print(dir(arm.animation_data))
+ #print("[["+dir(arm.animation_data.action))
+ if not arm.animation_data:
+ print("======================================")
+ print("Check Animation Data: None")
+ print("Armature has no animation, skipping...")
+ print("======================================")
+ break
+
+ if not arm.animation_data.action:
+ print("======================================")
+ print("Check Action: None")
+ print("Armature has no animation, skipping...")
+ print("======================================")
+ break
+ act = arm.animation_data.action
+ #print(dir(act))
+ action_name = act.name
+
+ if not len(act.fcurves):
+ print("//===========================================================")
+ print("// None bone pose set keys for this action set... skipping...")
+ print("//===========================================================")
+ bHaveAction = False
+
+ #this deal with action export control
+ if bHaveAction == True:
+ print("")
+ print("==== Action Set ====")
+ print("Action Name:",action_name)
+ #look for min and max frame that current set keys
+ framemin, framemax = act.get_frame_range()
+ #print("max frame:",framemax)
+ start_frame = framemin
+ end_frame = framemax
+ scene_frames = range(start_frame, end_frame+1)
+ frame_count = len(scene_frames)
+ #===================================================
+ anim = AnimInfoBinary()
+ anim.Name = action_name
+ anim.Group = "" #what is group?
+ anim.NumRawFrames = frame_count
+ anim.AnimRate = anim_rate
+ anim.FirstRawFrame = cur_frame_index
+ #===================================================
+ count_previous_keys = len(psa_file.RawKeys.Data)
+ print("Frame Key Set Count:",frame_count, "Total Frame:",frame_count)
+ #print("init action bones...")
+ unique_bone_indexes = {}
+ # bone lookup table
+ bones_lookup = {}
+
+ #build bone node for animation keys needed to be set
+ for bone in arm.data.bones:
+ bones_lookup[bone.name] = bone
+ #print("bone name:",bone.name)
+ frame_count = len(scene_frames)
+ #print ('Frame Count: %i' % frame_count)
+ pose_data = arm.pose
+
+ #these must be ordered in the order the bones will show up in the PSA file!
+ ordered_bones = {}
+ ordered_bones = sorted([(psa_file.UseBone(x.name), x) for x in pose_data.bones], key=operator.itemgetter(0))
+
+ #############################
+ # ORDERED FRAME, BONE
+ #for frame in scene_frames:
+
+ for i in range(frame_count):
+ frame = scene_frames[i]
+ #LOUD
+ #print ("==== outputting frame %i ===" % frame)
+
+ if frame_count > i+1:
+ next_frame = scene_frames[i+1]
+ #print "This Frame: %i, Next Frame: %i" % (frame, next_frame)
+ else:
+ next_frame = -1
+ #print "This Frame: %i, Next Frame: NONE" % frame
+
+ #frame start from 1 as number one from blender
+ blender_scene.set_frame(frame)
+
+ cur_frame_index = cur_frame_index + 1
+ for bone_data in ordered_bones:
+ bone_index = bone_data[0]
+ pose_bone = bone_data[1]
+ #print("[=====POSE NAME:",pose_bone.name)
+
+ #print("LENG >>.",len(bones_lookup))
+ blender_bone = bones_lookup[pose_bone.name]
+
+ #just need the total unique bones used, later for this AnimInfoBinary
+ unique_bone_indexes[bone_index] = bone_index
+ #LOUD
+ #print ("-------------------", pose_bone.name)
+ head = pose_bone.head
+
+ posebonemat = mathutils.Matrix(pose_bone.matrix)
+ parent_pose = pose_bone.parent
+ if parent_pose != None:
+ parentposemat = mathutils.Matrix(parent_pose.matrix)
+ #blender 2.4X it been flip around with new 2.50 (mat1 * mat2) should now be (mat2 * mat1)
+ posebonemat = parentposemat.invert() * posebonemat
+ head = posebonemat.translation_part()
+ quat = posebonemat.to_quat().normalize()
+ vkey = VQuatAnimKey()
+ vkey.Position.X = head.x
+ vkey.Position.Y = head.y
+ vkey.Position.Z = head.z
+
+ if parent_pose != None:
+ quat = make_fquat(quat)
+ else:
+ quat = make_fquat_default(quat)
+
+ vkey.Orientation = quat
+ #print("Head:",head)
+ #print("Orientation",quat)
+
+ #time from now till next frame = diff / framesPerSec
+ if next_frame >= 0:
+ diff = next_frame - frame
+ else:
+ diff = 1.0
+
+ #print ("Diff = ", diff)
+ vkey.Time = float(diff)/float(anim_rate)
+
+ psa_file.AddRawKey(vkey)
+
+ #done looping frames
+ #done looping armatures
+ #continue adding animInfoBinary counts here
+
+ anim.TotalBones = len(unique_bone_indexes)
+ print("Bones Count:",anim.TotalBones)
+ anim.TrackTime = float(frame_count) / anim.AnimRate
+ print("Time Track Frame:",anim.TrackTime)
+ psa_file.AddAnimation(anim)
+ print("==== Finish Action Build(s) ====")
+
+exportmessage = "Export Finish"
+
+def fs_callback(filename, context, user_setting):
+ #this deal with repeat export and the reset settings
+ global bonedata, BBCount, nbone, exportmessage
+ bonedata = []#clear array
+ BBCount = 0
+ nbone = 0
+
+ start_time = time.clock()
+
+ print ("========EXPORTING TO UNREAL SKELETAL MESH FORMATS========\r\n")
+ print("Blender Version:", bpy.app.version_string)
+
+ psk = PSKFile()
+ psa = PSAFile()
+
+ #sanity check - this should already have the extension, but just in case, we'll give it one if it doesn't
+ psk_filename = make_filename_ext(filename, '.psk')
+
+ #make the psa filename
+ psa_filename = make_filename_ext(filename, '.psa')
+
+ print ('PSK File: ' + psk_filename)
+ print ('PSA File: ' + psa_filename)
+
+ barmature = True
+ bmesh = True
+ blender_meshes = []
+ blender_armature = []
+ selectmesh = []
+ selectarmature = []
+
+ current_scene = context.scene
+ cur_frame = current_scene.frame_current #store current frame before we start walking them during animation parse
+ objects = current_scene.objects
+
+ print("Checking object count...")
+ for next_obj in objects:
+ if next_obj.type == 'MESH':
+ blender_meshes.append(next_obj)
+ if (next_obj.select):
+ #print("mesh object select")
+ selectmesh.append(next_obj)
+ if next_obj.type == 'ARMATURE':
+ blender_armature.append(next_obj)
+ if (next_obj.select):
+ #print("armature object select")
+ selectarmature.append(next_obj)
+
+ print("Mesh Count:",len(blender_meshes)," Armature Count:",len(blender_armature))
+ print("====================================")
+ print("Checking Mesh Condtion(s):")
+ if len(blender_meshes) == 1:
+ print(" - One Mesh Scene")
+ elif (len(blender_meshes) > 1) and (len(selectmesh) == 1):
+ print(" - One Mesh [Select]")
+ else:
+ print(" - Too Many Meshes!")
+ print(" - Select One Mesh Object!")
+ bmesh = False
+ print("====================================")
+ print("Checking Armature Condtion(s):")
+ if len(blender_armature) == 1:
+ print(" - One Armature Scene")
+ elif (len(blender_armature) > 1) and (len(selectarmature) == 1):
+ print(" - One Armature [Select]")
+ else:
+ print(" - Too Armature Meshes!")
+ print(" - Select One Armature Object Only!")
+ barmature = False
+
+ if (bmesh == False) or (barmature == False):
+ exportmessage = "Export Fail! Check Log."
+ print("=================================")
+ print("= Export Fail! =")
+ print("=================================")
+ else:
+ exportmessage = "Export Finish!"
+ #need to build a temp bone index for mesh group vertex
+ BoneIndexArmature(blender_armature)
+
+ try:
+ #######################
+ # STEP 1: MESH DUMP
+ # we build the vertexes, wedges, and faces in here, as well as a vertexgroup lookup table
+ # for the armature parse
+ print("//===============================")
+ print("// STEP 1")
+ print("//===============================")
+ parse_meshes(blender_meshes, psk)
+ except:
+ context.scene.set_frame(cur_frame) #set frame back to original frame
+ print ("Exception during Mesh Parse")
+ raise
+
+ try:
+ #######################
+ # STEP 2: ARMATURE DUMP
+ # IMPORTANT: do this AFTER parsing meshes - we need to use the vertex group data from
+ # the mesh parse in here to generate bone influences
+ print("//===============================")
+ print("// STEP 2")
+ print("//===============================")
+ parse_armature(blender_armature, psk, psa)
+
+ except:
+ context.scene.set_frame(cur_frame) #set frame back to original frame
+ print ("Exception during Armature Parse")
+ raise
+
+ try:
+ #######################
+ # STEP 3: ANIMATION DUMP
+ # IMPORTANT: do AFTER parsing bones - we need to do bone lookups in here during animation frames
+ print("//===============================")
+ print("// STEP 3")
+ print("//===============================")
+ parse_animation(current_scene, blender_armature, psa)
+
+ except:
+ context.scene.set_frame(cur_frame) #set frame back to original frame
+ print ("Exception during Animation Parse")
+ raise
+
+ # reset current frame
+
+ context.scene.set_frame(cur_frame) #set frame back to original frame
+
+ ##########################
+ # FILE WRITE
+ print("//===========================================")
+ print("// bExportPsk:",bpy.context.scene.unrealexportpsk," bExportPsa:",bpy.context.scene.unrealexportpsa)
+ print("//===========================================")
+ if bpy.context.scene.unrealexportpsk == True:
+ print("Writing Skeleton Mesh Data...")
+ #RG - dump psk file
+ psk.PrintOut()
+ file = open(psk_filename, "wb")
+ file.write(psk.dump())
+ file.close()
+ print ("Successfully Exported File: " + psk_filename)
+ if bpy.context.scene.unrealexportpsa == True:
+ print("Writing Animaiton Data...")
+ #RG - dump psa file
+ if not psa.IsEmpty():
+ psa.PrintOut()
+ file = open(psa_filename, "wb")
+ file.write(psa.dump())
+ file.close()
+ print ("Successfully Exported File: " + psa_filename)
+ else:
+ print ("No Animations (.psa file) to Export")
+
+ print ('PSK/PSA Export Script finished in %.2f seconds' % (time.clock() - start_time))
+
+ #MSG BOX EXPORT COMPLETE
+ #...
+
+ #DONE
+ print ("PSK/PSA Export Complete")
+
+def write_data(path, context, user_setting):
+ print("//============================")
+ print("// running psk/psa export...")
+ print("//============================")
+ fs_callback(path, context, user_setting)
+ pass
+
+from bpy.props import *
+
+exporttypedata = []
+
+# [index,text field,0] #or something like that
+exporttypedata.append(("0","PSK","Export PSK"))
+exporttypedata.append(("1","PSA","Export PSA"))
+exporttypedata.append(("2","ALL","Export ALL"))
+
+IntProperty= bpy.types.Scene.IntProperty
+
+IntProperty(attr="unrealfpsrate", name="fps rate",
+ description="Set the frame per second (fps) for unreal.",
+ default=24,min=1,max=100)
+
+bpy.types.Scene.EnumProperty( attr="unrealexport_settings",
+ name="Export:",
+ description="Select a export settings (psk/psa/all)...",
+ items = exporttypedata, default = '0')
+
+bpy.types.Scene.BoolProperty( attr="unrealtriangulatebool",
+ name="Triangulate Mesh",
+ description="Convert Quad to Tri Mesh Boolean...",
+ default=False)
+
+bpy.types.Scene.BoolProperty( attr="unrealactionexportall",
+ name="All Actions",
+ description="This let you export all actions from current armature.[Not Build Yet]",
+ default=False)
+
+bpy.types.Scene.BoolProperty( attr="unrealexportpsk",
+ name="bool export psa",
+ description="bool for exporting this psk format",
+ default=False)
+
+bpy.types.Scene.BoolProperty( attr="unrealexportpsa",
+ name="bool export psa",
+ description="bool for exporting this psa format",
+ default=False)
+
+class ExportUDKAnimData(bpy.types.Operator):
+ global exportmessage
+ '''Export Skeleton Mesh / Animation Data file(s)'''
+ bl_idname = "export.udk_anim_data" # this is important since its how bpy.ops.export.udk_anim_data is constructed
+ bl_label = "Export PSK/PSA"
+ __doc__ = "One mesh and one armature else select one mesh or armature to be exported."
+
+ # List of operator properties, the attributes will be assigned
+ # to the class instance from the operator settings before calling.
+
+ filepath = StringProperty(name="File Path", description="Filepath used for exporting the PSA file", maxlen= 1024, default= "")
+ use_setting = BoolProperty(name="No Options Yet", description="No Options Yet", default= True)
+ pskexportbool = BoolProperty(name="Export PSK", description="Export Skeletal Mesh", default= True)
+ psaexportbool = BoolProperty(name="Export PSA", description="Export Action Set (Animation Data)", default= True)
+ actionexportall = BoolProperty(name="All Actions", description="This will export all the actions that matches the current armature.", default=False)
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ #check if skeleton mesh is needed to be exported
+ if (self.properties.pskexportbool):
+ bpy.context.scene.unrealexportpsk = True
+ else:
+ bpy.context.scene.unrealexportpsk = False
+ #check if animation data is needed to be exported
+ if (self.properties.psaexportbool):
+ bpy.context.scene.unrealexportpsa = True
+ else:
+ bpy.context.scene.unrealexportpsa = False
+
+ write_data(self.properties.filepath, context, self.properties.use_setting)
+
+ self.report({'WARNING', 'INFO'}, exportmessage)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.manager
+ wm.add_fileselect(self)
+ return {'RUNNING_MODAL'}
+
+def menu_func(self, context):
+ bpy.context.scene.unrealexportpsk = True
+ bpy.context.scene.unrealexportpsa = True
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".psk"
+ self.layout.operator("export.udk_anim_data", text="Skeleton Mesh / Animation Data (.psk/.psa)").filepath = default_path
+
+
+class VIEW3D_PT_unrealtools_objectmode(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "Unreal Tools"
+
+ def poll(self, context):
+ return context.active_object
+
+ def draw(self, context):
+ layout = self.layout
+ #layout.label(text="Unreal Tools")
+ rd = context.scene
+ #drop box
+ layout.prop(rd, "unrealexport_settings",expand=True)
+ #layout.prop(rd, "unrealexport_settings")
+ #button
+ layout.operator("object.UnrealExport")
+ #FPS #it use the real data from your scene
+ layout.prop(rd.render, "fps")
+
+ layout.prop(rd, "unrealactionexportall")
+ #row = layout.row()
+ #row.label(text="Action Set(s)(not build)")
+ #for action in bpy.data.actions:
+ #print(dir( action))
+ #print(action.get_frame_range())
+ #row = layout.row()
+ #row.prop(action, "name")
+
+ #print(dir(action.groups[0]))
+ #for g in action.groups:#those are bones
+ #print("group...")
+ #print(dir(g))
+ #print("////////////")
+ #print((g.name))
+ #print("////////////")
+
+ #row.label(text="Active:" + action.select)
+ btrimesh = False
+
+class OBJECT_OT_UnrealExport(bpy.types.Operator):
+ global exportmessage
+ bl_idname = "OBJECT_OT_UnrealExport"
+ bl_label = "Unreal Export"
+ __doc__ = "Select export setting for .psk/.psa or both."
+
+ def invoke(self, context, event):
+ #path = StringProperty(name="File Path", description="File path used for exporting the PSA file", maxlen= 1024, default= "")
+ print("Init Export Script:")
+ if(int(bpy.context.scene.unrealexport_settings) == 0):
+ bpy.context.scene.unrealexportpsk = True
+ bpy.context.scene.unrealexportpsa = False
+ print("Exporting PSK...")
+ if(int(bpy.context.scene.unrealexport_settings) == 1):
+ bpy.context.scene.unrealexportpsk = False
+ bpy.context.scene.unrealexportpsa = True
+ print("Exporting PSA...")
+ if(int(bpy.context.scene.unrealexport_settings) == 2):
+ bpy.context.scene.unrealexportpsk = True
+ bpy.context.scene.unrealexportpsa = True
+ print("Exporting ALL...")
+
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".psk"
+ fs_callback(default_path, bpy.context, False)
+
+ #self.report({'WARNING', 'INFO'}, exportmessage)
+ self.report({'INFO'}, exportmessage)
+ return{'FINISHED'}
+
+def register():
+ global MENUPANELBOOL
+ if MENUPANELBOOL:
+ bpy.types.register(OBJECT_OT_UnrealExport)
+ bpy.types.register(VIEW3D_PT_unrealtools_objectmode)
+ bpy.types.register(ExportUDKAnimData)
+ bpy.types.INFO_MT_file_export.append(menu_func)
+
+def unregister():
+ global MENUPANELBOOL
+ if MENUPANELBOOL:
+ bpy.types.unregister(OBJECT_OT_UnrealExport)
+ bpy.types.unregister(VIEW3D_PT_unrealtools_objectmode)
+ bpy.types.unregister(ExportUDKAnimData)
+ bpy.types.INFO_MT_file_export.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/io_import_images_as_planes.py b/io_import_images_as_planes.py
new file mode 100644
index 00000000..555c9476
--- /dev/null
+++ b/io_import_images_as_planes.py
@@ -0,0 +1,528 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+This script imports images and creates Planes with them as textures.
+At the moment the naming for objects, materials, textures and meshes
+is derived from the imagename.
+
+One can either import a single image, or all images in one directory.
+When imporing a directory one can either check the checkbox or leave
+the filename empty.
+
+As a bonus one can choose to import images of only one type.
+Atm this is the list of possible extensions:
+extList =
+ ('jpeg', 'jpg', 'png', 'tga', 'tiff', 'tif', 'exr',
+ 'hdr', 'avi', 'mov', 'mp4', 'ogg', 'bmp', 'cin', 'dpx', 'psd')
+
+If someone knows a better way of telling if a file is an image which
+Blender can read, please tell so ;)
+
+when importing images that are allready referenced they are not
+reimported but the old ones reused as not to clutter the materials,
+textures and image lists.
+Instead the plane gets linked against an existing material.
+
+If one reimports images but chooses different material/texture mapping
+new materials are created.
+So one doesn't has to go through everything if one decides differently
+after importing 236 images.
+
+It also has an option to translate pixeldimensions into Blenderunits.
+"""
+
+bl_addon_info = {
+ 'name': 'Import: Images as Planes',
+ 'author': 'Florian Meyer (testscreenings)',
+ 'version': '0.7',
+ 'blender': (2, 5, 3),
+ 'location': 'File > Import > Images as Planes',
+ 'description': 'Imports images and creates planes' \
+ ' with the appropiate aspect ratio.' \
+ ' The images are mapped to the planes.',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Add_Mesh/Planes_from_Images',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21751&group_id=153&atid=469',
+ 'category': 'Import/Export'}
+
+import bpy
+from bpy.props import *
+from os import listdir
+from mathutils import Vector
+
+
+EXT_LIST = {
+ 'jpeg': ['jpeg', 'jpg', 'jpe'],
+ 'png': ['png'],
+ 'tga': ['tga', 'tpic'],
+ 'tiff': ['tiff', 'tif'],
+ 'exr': ['exr'],
+ 'hdr': ['hdr'],
+ 'avi': ['avi'],
+ 'mov': ['mov', 'qt'],
+ 'mp4': ['mp4'],
+ 'ogg': ['ogg', 'ogv'],
+ 'bmp': ['bmp', 'dib'],
+ 'cin': ['cin'],
+ 'dpx': ['dpx'],
+ 'psd': ['psd']}
+
+
+# Apply view rotation to objects if "Align To" for new objects
+# was set to "VIEW" in the User Preference.
+def apply_view_rotation(ob):
+ context = bpy.context
+ align = bpy.context.user_preferences.edit.object_align
+
+ if (context.space_data.type == 'VIEW_3D'
+ and align == 'VIEW'):
+ view3d = context.space_data
+ region = view3d.region_3d
+ viewMatrix = region.view_matrix
+ rot = viewMatrix.rotation_part()
+ ob.rotation_euler = rot.invert().to_euler()
+
+
+# Create plane mesh
+def createPlaneMesh(dimension, img):
+ # x is the x-aspectRatio.
+ x = img.size[0] / img.size[1]
+ y = 1
+
+ if dimension[0]:
+ x = (img.size[0] * (1.0 / dimension[1])) * 0.5
+ y = (img.size[1] * (1.0 / dimension[1])) * 0.5
+
+ verts = []
+ faces = []
+
+ v1 = (-x, -y, 0)
+ v2 = (x, -y, 0)
+ v3 = (x, y, 0)
+ v4 = (-x, y, 0)
+
+ verts.append(v1)
+ verts.append(v2)
+ verts.append(v3)
+ verts.append(v4)
+
+ faces.append([0, 1, 2, 3])
+
+ return verts, faces
+
+
+# Create plane object
+def createPlaneObj(img, dimension):
+ scene = bpy.context.scene
+
+ verts, faces = createPlaneMesh(dimension, img)
+
+ me = bpy.data.meshes.new(img.name)
+ me.from_pydata(verts, [], faces)
+ me.update()
+
+ plane = bpy.data.objects.new(img.name, me)
+ plane.data.add_uv_texture()
+
+ scene.objects.link(plane)
+ plane.location = scene.cursor_location
+ apply_view_rotation(plane)
+
+ return plane
+
+
+# Check if a file extension matches any
+# valid (i.e. recognized) image/movie format.
+def isImageFile(extension):
+ for ext, ext_list in EXT_LIST.items():
+ if extension in ext_list:
+ return True
+
+ return False
+
+
+# Get imagepaths from directory
+def getImageFilesInDirectory(directory, extension):
+ import os
+
+ # Get all files in the directory.
+ allFiles = listdir(directory)
+ allImages = []
+
+ extensions = []
+
+ # Import all images files?
+ if extension == '*':
+ all = True
+
+ else:
+ all = False
+ # Get the possible extensions
+ extensions = EXT_LIST[extension]
+
+ # Put all image files in the list.
+ for file in allFiles:
+ # Get the file extension (includes the ".")
+ e = os.path.splitext(file)[1]
+
+ # Separate by "." and get the last list-entry.
+ e = e.rpartition(".")[-1]
+
+ # Convert to lower case
+ e = e.lower()
+
+ if (e in extensions
+ or (all and isImageFile(e))):
+ allImages.append(file)
+
+ return allImages
+
+
+# Get image datablock from the (image's) filepath.
+def getImage(path):
+ img = []
+
+ # Check every Image if it is already there.
+ for image in bpy.data.images:
+ # If image with same path exists take that one.
+ if image.filepath == path:
+ img = image
+
+ # Else create new Image and load from path.
+ if not img:
+ name = path.rpartition('\\')[2].rpartition('.')[0]
+ img = bpy.data.images.new(name)
+ img.source = 'FILE'
+ img.filepath = path
+
+ return img
+
+
+# Create/get Material
+def getMaterial(tex, mapping):
+ mat = []
+
+ # Check all existing materials.
+ for material in bpy.data.materials:
+ # If a material with name and mapping
+ # texture with image exists, take that one...
+ if (material.name == tex.image.name
+ and tex.name in material.texture_slots
+ and material.mapping == mapping):
+ mat = material
+
+ # ... otherwise create new one and apply mapping.
+ if not mat:
+ mat = bpy.data.materials.new(name=tex.name)
+ mat.add_texture(tex, texture_coordinates='UV', map_to='COLOR')
+ mat.mapping = mapping
+ mat.name = tex.name
+
+ return mat
+
+
+# Create/get Texture
+def getTexture(path, img):
+ tex = []
+
+ # Check all existing textures.
+ for texture in bpy.data.textures:
+ # If an (image)texture with image exists, take that one...
+ if (texture.type == 'IMAGE'
+ and texture.image
+ and texture.image.filepath == path):
+ tex = texture
+
+ # ... otherwise create a new one and apply mapping.
+ if not tex:
+ name = path.rpartition('\\')[2].rpartition('.')[0]
+ tex = bpy.data.textures.new(name=name)
+ tex.type = 'IMAGE'
+ tex = tex.recast_type()
+ tex.image = img
+
+ return tex
+
+
+# Custom material property - get
+def mapget(self):
+ """Custom property of the images_as_planes addon."""
+ mapping = []
+ mapping.append(self.shadeless)
+ mapping.append(self.transparency)
+ mapping.append(self.alpha)
+ mapping.append(self.specular_alpha)
+ mapping.append(self.transparency_method)
+
+ if (self.texture_slots[0]
+ and self.texture_slots[0].texture.type == 'IMAGE'
+ and self.texture_slots[0].texture.image):
+ mapping.append(self.texture_slots[0].texture.image.premultiply)
+
+ else:
+ mapping.append("no image")
+
+ return mapping
+
+
+# Custom material property - set
+def mapset(self, value):
+ self.shadeless = value[0]
+ self.transparency = value[1]
+ self.alpha = float(value[2])
+ self.specular_alpha = float(value[3])
+ self.transparency_method = value[4]
+
+ if (self.texture_slots[0]
+ and self.texture_slots[0].texture.type == 'IMAGE'
+ and self.texture_slots[0].texture.image):
+ self.texture_slots[0].texture.image.premultiply = value[5]
+ if self.alpha:
+ self.texture_slots[0].map_alpha=True
+
+
+bpy.types.Material.mapping = property(mapget, mapset)
+
+
+def main(filePath, options, mapping, dimension):
+ images = []
+ scene = bpy.context.scene
+
+ # If "Create from Directory" (no filepath or checkbox) ####
+ if options['dir'] or not filePath[1]:
+ imageFiles = getImageFilesInDirectory(filePath[2], options['ext'])
+
+ # Check if images are loaded and put them in the list.
+ for imageFile in imageFiles:
+ img = getImage(str(filePath[2]) + "\\" + str(imageFile))
+ images.append(img)
+
+ # Deselect all objects.
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Assign/get all things.
+ for img in images:
+ # Create/get Texture
+ tex = getTexture(img.filepath, img)
+
+ # Create/get Material
+ mat = getMaterial(tex, mapping)
+
+ # Create Plane
+ plane = createPlaneObj(img, dimension)
+
+ # Assign Material
+ plane.data.add_material(mat)
+
+ # Put Image into UVTextureLayer
+ plane.data.uv_textures[0].data[0].image = img
+ plane.data.uv_textures[0].data[0].tex = True
+ plane.data.uv_textures[0].data[0].transp = 'ALPHA'
+ plane.data.uv_textures[0].data[0].twoside = True
+
+ plane.select = True
+ scene.objects.active = plane
+
+ # If "Create Single Plane" (filepath and is image)
+ else:
+ # Deselect all objects.
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Check if image is loaded.
+ img = getImage(filePath[0])
+
+ # Create/get Texture
+ tex = getTexture(filePath[0], img)
+
+ # Create/get Material
+ mat = getMaterial(tex, mapping)
+
+ # Create Plane
+ plane = createPlaneObj(img, dimension)
+
+ # Assign Material
+ plane.data.add_material(mat)
+
+ # Put image into UVTextureLayer
+ plane.data.uv_textures[0].data[0].image = img
+ plane.data.uv_textures[0].data[0].tex = True
+ plane.data.uv_textures[0].data[0].transp = 'ALPHA'
+ plane.data.uv_textures[0].data[0].twoside = True
+
+ plane.select = True
+ scene.objects.active = plane
+
+
+# Operator
+class ImportImagesAsPlanes(bpy.types.Operator):
+ ''''''
+ bl_idname = "import.images_as_planes"
+ bl_label = "Import Images as Planes"
+ bl_description = "Create mesh plane(s) from image files" \
+ " with the appropiate aspect ratio."
+ bl_options = {'REGISTER', 'UNDO'}
+
+ filepath = StringProperty(name="File Path",
+ description="Filepath used for importing the file",
+ maxlen=1024,
+ default="")
+ filename = StringProperty(name="File Name",
+ description="Name of the file.")
+ directory = StringProperty(name="Directory",
+ description="Directory of the file.")
+ fromDirectory = BoolProperty(name="All in directory",
+ description="Import all image files (of the selected type)" \
+ " in this directory.",
+ default=False)
+
+ extEnum = [
+ ('*', 'All image formats',
+ 'Import all know image (or movie) formats.'),
+ ('jpeg', 'JPEG (.jpg, .jpeg, .jpe)',
+ 'Joint Photographic Experts Group'),
+ ('png', 'PNG (.png)', 'Portable Network Graphics'),
+ ('tga', 'Truevision TGA (.tga, tpic)', ''),
+ ('tiff', 'TIFF (.tif, .tiff)', 'Tagged Image File Format'),
+ ('exr', 'OpenEXR (.exr)', 'OpenEXR HDR imaging image file format'),
+ ('hdr', 'Radiance HDR (.hdr, .pic)', ''),
+ ('avi', 'AVI (.avi)', 'Audio Video Interleave'),
+ ('mov', 'QuickTime (.mov, .qt)', ''),
+ ('mp4', 'MPEG-4 (.mp4)', ' MPEG-4 Part 14'),
+ ('ogg', 'OGG Theora (.ogg, .ogv)', ''),
+ ('bmp', 'BMP (.bmp, .dib)', 'Windows Bitmap'),
+ ('cin', 'CIN (.cin)', ''),
+ ('dpx', 'DPX (.dpx)', 'DPX (Digital Picture Exchange)'),
+ ('psd', 'PSD (.psd)', 'Photoshop Document')]
+ extension = EnumProperty(name="Extension",
+ description="Only import files of this type.",
+ items=extEnum)
+
+ shadeless = BoolProperty(name="Shadeless",
+ description="Set material to shadeless",
+ default=False)
+ transp = BoolProperty(name="Use alpha",
+ description="Use alphachannel for transparency.",
+ default=False)
+ premultiply = BoolProperty(name="Premultiply",
+ description="Premultiply image",
+ default=False)
+
+ tEnum = [
+ ('Z_TRANSPARENCY',
+ 'Z Transparency',
+ 'Use alpha buffer for transparent faces'),
+ ('RAYTRACE',
+ 'Raytrace',
+ 'Use raytracing for transparent refraction rendering.')]
+ transp_method = EnumProperty(name="Transp. Method",
+ description="Transparency Method",
+ items=tEnum)
+ useDim = BoolProperty(name="Use image dimensions",
+ description="Use the images pixels to derive the size of the plane.",
+ default=False)
+ factor = IntProperty(name="Pixels/BU",
+ description="Number of pixels per Blenderunit.",
+ min=1,
+ default=500)
+
+ def draw(self, context):
+ props = self.properties
+ layout = self.layout
+ box = layout.box()
+ box.label('Filter:', icon='FILTER')
+ box.prop(props, 'fromDirectory')
+ box.prop(props, 'extension', icon='FILE_IMAGE')
+ box = layout.box()
+ box.label('Material mappings:', icon='MATERIAL')
+ box.prop(props, 'shadeless')
+ box.prop(props, 'transp')
+ box.prop(props, 'premultiply')
+ box.prop(props, 'transp_method', expand=True)
+ box = layout.box()
+ box.label('Plane dimensions:', icon='ARROW_LEFTRIGHT')
+ box.prop(props, 'useDim')
+ box.prop(props, 'factor', expand=True)
+
+ def execute(self, context):
+ # File Path
+ filepath = self.properties.filepath
+ filename = self.properties.filename
+ directory = self.properties.directory
+ filePath = (filepath, filename, directory)
+
+ # General Options
+ fromDirectory = self.properties.fromDirectory
+ extension = self.properties.extension
+ options = {'dir': fromDirectory, 'ext': extension}
+
+ # Mapping
+ alphavalue = 1
+ transp = self.properties.transp
+ if transp:
+ alphavalue = 0
+
+ shadeless = self.properties.shadeless
+ transp_method = self.properties.transp_method
+ premultiply = self.properties.premultiply
+
+ mapping = ([shadeless,
+ transp,
+ alphavalue,
+ alphavalue,
+ transp_method,
+ premultiply])
+
+ # Use Pixelsdimensions
+ useDim = self.properties.useDim
+ factor = self.properties.factor
+ dimension = (useDim, factor)
+
+ # Call Main Function
+ main(filePath, options, mapping, dimension)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = bpy.context.manager
+ wm.add_fileselect(self)
+
+ return {'RUNNING_MODAL'}
+
+
+# Registering / Unregister
+menu_func = (lambda self, context: self.layout.operator(
+ ImportImagesAsPlanes.bl_idname,
+ text="Images as Planes",
+ icon='PLUGIN'))
+
+
+def register():
+ bpy.types.register(ImportImagesAsPlanes)
+ bpy.types.INFO_MT_file_import.append(menu_func)
+
+
+def unregister():
+ bpy.types.unregister(ImportImagesAsPlanes)
+ bpy.types.INFO_MT_file_import.remove(menu_func)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/io_import_scene_mhx.py b/io_import_scene_mhx.py
new file mode 100644
index 00000000..9800b99c
--- /dev/null
+++ b/io_import_scene_mhx.py
@@ -0,0 +1,2246 @@
+"""
+**Project Name:** MakeHuman
+
+**Product Home Page:** http://www.makehuman.org/
+
+**Code Home Page:** http://code.google.com/p/makehuman/
+
+**Authors:** Thomas Larsson
+
+**Copyright(c):** MakeHuman Team 2001-2010
+
+**Licensing:** GPL3 (see also http://sites.google.com/site/makehumandocs/licensing)
+
+**Coding Standards:** See http://sites.google.com/site/makehumandocs/developers-guide
+
+Abstract
+MHX (MakeHuman eXchange format) importer for Blender 2.5x.
+Version 0.9
+
+"""
+
+bl_addon_info = {
+ 'name': 'Import MakeHuman (.mhx)',
+ 'author': 'Thomas Larsson',
+ 'version': '0.9, Make Human Alpha 5',
+ 'blender': (2, 5, 3),
+ 'location': 'File > Import',
+ 'description': 'Import files in the MakeHuman eXchange format (.mhx)',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/File_I-O/Make_Human',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21872&group_id=153&atid=469',
+ 'category': 'Import/Export'}
+
+"""
+Place this file in the .blender/scripts/addons dir
+You have to activated the script in the "Add-Ons" tab (user preferences).
+Access from the File > Import menu.
+"""
+
+#
+#
+#
+
+import bpy
+import os
+import time
+import mathutils
+from mathutils import *
+import geometry
+import string
+
+MAJOR_VERSION = 0
+MINOR_VERSION = 9
+MHX249 = False
+Blender24 = False
+Blender25 = True
+TexDir = "~/makehuman/exports"
+
+#
+#
+#
+
+theScale = 1.0
+useMesh = 1
+doSmash = 1
+verbosity = 2
+warnedTextureDir = False
+warnedVersion = False
+
+true = True
+false = False
+Epsilon = 1e-6
+nErrors = 0
+theTempDatum = None
+
+todo = []
+
+#
+# toggle flags
+#
+
+T_ArmIK = 0x01
+T_LegIK = 0x02
+T_Replace = 0x20
+T_Face = 0x40
+T_Shape = 0x80
+T_Mesh = 0x100
+T_Armature = 0x200
+T_Proxy = 0x400
+T_Panel = 0x800
+
+T_Rigify = 0x1000
+T_Preset = 0x2000
+T_Symm = 0x4000
+T_MHX = 0x8000
+
+toggle = T_Replace + T_ArmIK + T_LegIK + T_Mesh + T_Armature + T_Face
+
+#
+# setFlagsAndFloats(rigFlags):
+#
+# Global floats
+fLegIK = 0.0
+fArmIK = 0.0
+fFingerPanel = 0.0
+fFingerIK = 0.0
+fFingerCurl = 0.0
+
+# rigLeg and rigArm flags
+T_Toes = 0x0001
+T_GoboFoot = 0x0002
+T_InvFoot = 0x0004
+
+T_FingerPanel = 0x100
+T_FingerCurl = 0x0200
+T_FingerIK = 0x0400
+
+
+T_LocalFKIK = 0x8000
+
+rigLeg = 0
+rigArm = 0
+
+def setFlagsAndFloats(rigFlags):
+ global toggle, rigLeg, rigArm
+
+ (footRig, fingerRig) = rigFlags
+ rigLeg = 0
+ rigArm = 0
+ if footRig == 'Reverse foot': rigLeg |= T_InvFoot
+ elif footRig == 'Gobo': rigLeg |= T_GoboFoot
+
+ if fingerRig == 'Panel': rigArm |= T_FingerPanel
+ elif fingerRig == 'IK': rigArm |= T_FingerIK
+ elif fingerRig == 'Curl': rigArm |= T_FingerCurl
+
+ toggle |= T_Panel
+
+ # Global floats, used as influences
+ global fFingerCurl, fLegIK, fArmIK, fFingerIK
+
+ fFingerCurl = 1.0 if rigArm&T_FingerCurl else 0.0
+ fLegIK = 1.0 if toggle&T_LegIK else 0.0
+ fArmIK = 1.0 if toggle&T_ArmIK else 0.0
+ fFingerIK = 1.0 if rigArm&T_FingerIK else 0.0
+
+ return
+
+
+#
+# Dictionaries
+#
+
+loadedData = {
+ 'NONE' : {},
+
+ 'Object' : {},
+ 'Mesh' : {},
+ 'Armature' : {},
+ 'Lamp' : {},
+ 'Camera' : {},
+ 'Lattice' : {},
+ 'Curve' : {},
+
+ 'Material' : {},
+ 'Image' : {},
+ 'MaterialTextureSlot' : {},
+ 'Texture' : {},
+
+ 'Bone' : {},
+ 'BoneGroup' : {},
+ 'Rigify' : {},
+
+ 'Action' : {},
+ 'Group' : {},
+
+ 'MeshTextureFaceLayer' : {},
+ 'MeshColorLayer' : {},
+ 'VertexGroup' : {},
+ 'ShapeKey' : {},
+ 'ParticleSystem' : {},
+
+ 'ObjectConstraints' : {},
+ 'ObjectModifiers' : {},
+ 'MaterialSlot' : {},
+}
+
+Plural = {
+ 'Object' : 'objects',
+ 'Mesh' : 'meshes',
+ 'Lattice' : 'lattices',
+ 'Curve' : 'curves',
+ 'Group' : 'groups',
+ 'Empty' : 'empties',
+ 'Armature' : 'armatures',
+ 'Bone' : 'bones',
+ 'BoneGroup' : 'bone_groups',
+ 'Pose' : 'poses',
+ 'PoseBone' : 'pose_bones',
+ 'Material' : 'materials',
+ 'Texture' : 'textures',
+ 'Image' : 'images',
+ 'Camera' : 'cameras',
+ 'Lamp' : 'lamps',
+ 'World' : 'worlds',
+}
+
+#
+# Creators
+#
+
+def uvtexCreator(me, name):
+ print("uvtexCreator", me, name)
+ me.add_uv_texture()
+ uvtex = me.uv_textures[-1]
+ uvtex.name = name
+ return uvtex
+
+
+def vertcolCreator(me, name):
+ print("vertcolCreator", me, name)
+ me.add_vertex_color()
+ vcol = me.vertex_colors[-1]
+ vcol.name = name
+ return vcol
+
+
+#
+# loadMhx(filePath, context, flags):
+#
+
+def loadMhx(filePath, context, flags):
+ global toggle
+ toggle = flags
+ readMhxFile(filePath)
+ return
+
+#
+# readMhxFile(filePath, rigFlags):
+#
+
+def readMhxFile(filePath, rigFlags):
+ global todo, nErrors
+
+ fileName = os.path.expanduser(filePath)
+ (shortName, ext) = os.path.splitext(fileName)
+ if ext != ".mhx":
+ print("Error: Not a mhx file: " + fileName)
+ return
+ print( "Opening MHX file "+ fileName )
+ time1 = time.clock()
+
+ ignore = False
+ stack = []
+ tokens = []
+ key = "toplevel"
+ level = 0
+ nErrors = 0
+
+ setFlagsAndFloats(rigFlags)
+
+ file= open(fileName, "rU")
+ print( "Tokenizing" )
+ lineNo = 0
+ for line in file:
+ # print(line)
+ lineSplit= line.split()
+ lineNo += 1
+ if len(lineSplit) == 0:
+ pass
+ elif lineSplit[0] == '#':
+ pass
+ elif lineSplit[0] == 'end':
+ try:
+ sub = tokens
+ tokens = stack.pop()
+ if tokens:
+ tokens[-1][2] = sub
+ level -= 1
+ except:
+ print( "Tokenizer error at or before line %d" % lineNo )
+ print( line )
+ dummy = stack.pop()
+ elif lineSplit[-1] == ';':
+ if lineSplit[0] == '\\':
+ key = lineSplit[1]
+ tokens.append([key,lineSplit[2:-1],[]])
+ else:
+ key = lineSplit[0]
+ tokens.append([key,lineSplit[1:-1],[]])
+ else:
+ key = lineSplit[0]
+ tokens.append([key,lineSplit[1:],[]])
+ stack.append(tokens)
+ level += 1
+ tokens = []
+ file.close()
+
+ if level != 0:
+ raise NameError("Tokenizer out of kilter %d" % level)
+ clearScene()
+ print( "Parsing" )
+ parse(tokens)
+
+ for (expr, glbals, lcals) in todo:
+ try:
+ # print("Doing %s" % expr)
+ exec(expr, glbals, lcals)
+ except:
+ msg = "Failed: "+expr
+ print( msg )
+ nErrors += 1
+ #raise NameError(msg)
+
+ print("Postprocess")
+ postProcess()
+ print("HideLayers")
+ hideLayers()
+ time2 = time.clock()
+ print("toggle = %x" % toggle)
+ msg = "File %s loaded in %g s" % (fileName, time2-time1)
+ if nErrors:
+ msg += " but there where %d errors. " % (nErrors)
+ print(msg)
+ return # loadMhx
+
+#
+# getObject(name, var, glbals, lcals):
+#
+
+def getObject(name, var, glbals, lcals):
+ try:
+ ob = loadedData['Object'][name]
+ except:
+ if name != "None":
+ expr = "%s = loadedData['Object'][name]" % var
+ print("Todo ", expr)
+ todo.append((expr, glbals, lcals))
+ ob = None
+ return ob
+
+#
+# parse(tokens):
+#
+
+ifResult = False
+
+def parse(tokens):
+ global warnedVersion, MHX249, ifResult
+
+ for (key, val, sub) in tokens:
+ # print("Parse %s" % key)
+ data = None
+ if key == 'MHX':
+ if int(val[0]) != MAJOR_VERSION and int(val[1]) != MINOR_VERSION and not warnedVersion:
+ print("Warning: \nThis file was created with another version of MHX\n")
+ warnedVersion = True
+
+ elif key == 'MHX249':
+ MHX249 = eval(val[0])
+ print("Blender 2.49 compatibility mode is %s\n" % MHX249)
+
+ elif key == 'if':
+ try:
+ ifResult = eval(val[0])
+ except:
+ ifResult = False
+ if ifResult:
+ parse(sub)
+
+ elif key == 'elif':
+ if not ifResult:
+ try:
+ ifResult = eval(val[0])
+ except:
+ ifResult = False
+ if ifResult:
+ parse(sub)
+
+ elif key == 'else':
+ if not ifResult:
+ parse(sub)
+
+
+ elif MHX249:
+ pass
+
+ elif key == 'print':
+ msg = concatList(val)
+ print(msg)
+ elif key == 'warn':
+ msg = concatList(val)
+ print(msg)
+ elif key == 'error':
+ msg = concatList(val)
+ raise NameError(msg)
+ elif key == "Object":
+ parseObject(val, sub)
+ elif key == "Mesh":
+ data = parseMesh(val, sub)
+ elif key == "Curve":
+ data = parseCurve(val, sub)
+ elif key == "Lattice":
+ data = parseLattice(val, sub)
+ elif key == "Group":
+ data = parseGroup(val, sub)
+ elif key == "Armature":
+ data = parseArmature(val, sub)
+ elif key == "Pose":
+ data = parsePose(val, sub)
+ elif key == "Action":
+ data = parseAction(val, sub)
+ elif key == "Material":
+ data = parseMaterial(val, sub)
+ elif key == "Texture":
+ data = parseTexture(val, sub)
+ elif key == "Image":
+ data = parseImage(val, sub)
+ elif key == "Process":
+ parseProcess(val, sub)
+ elif key == 'AnimationData':
+ try:
+ ob = loadedData['Object'][val[0]]
+ except:
+ ob = None
+ if ob:
+ bpy.context.scene.objects.active = ob
+ parseAnimationData(ob, sub)
+ elif key == 'ShapeKeys':
+ try:
+ ob = loadedData['Object'][val[0]]
+ except:
+ ob = None
+ if ob:
+ bpy.context.scene.objects.active = ob
+ parseShapeKeys(ob, ob.data, val, sub)
+ else:
+ data = parseDefaultType(key, val, sub)
+
+ if data and key != 'Mesh':
+ print( data )
+ return
+
+#
+# parseDefaultType(typ, args, tokens):
+#
+
+def parseDefaultType(typ, args, tokens):
+ global todo
+
+ name = args[0]
+ data = None
+ expr = "bpy.data.%s.new('%s')" % (Plural[typ], name)
+ print(expr)
+ data = eval(expr)
+ print(" ok", data)
+
+ bpyType = typ.capitalize()
+ print(bpyType, name, data)
+ loadedData[bpyType][name] = data
+ if data == None:
+ return None
+
+ for (key, val, sub) in tokens:
+ #print("%s %s" % (key, val))
+ defaultKey(key, val, sub, 'data', [], globals(), locals())
+ print("Done ", data)
+ return data
+
+#
+# concatList(elts)
+#
+
+def concatList(elts):
+ string = ""
+ for elt in elts:
+ string += " %s" % elt
+ return string
+
+#
+# parseAction(args, tokens):
+# parseFCurve(fcu, args, tokens):
+# parseKeyFramePoint(pt, args, tokens):
+#
+
+def parseAction(args, tokens):
+ name = args[0]
+ if invalid(args[1]):
+ return
+
+ ob = bpy.context.object
+ bpy.ops.object.mode_set(mode='POSE')
+ if ob.animation_data:
+ ob.animation_data.action = None
+ created = {}
+ for (key, val, sub) in tokens:
+ if key == 'FCurve':
+ prepareActionFCurve(ob, created, val, sub)
+
+ act = ob.animation_data.action
+ loadedData['Action'][name] = act
+ if act == None:
+ print("Ignoring action %s" % name)
+ return act
+ act.name = name
+ print("Action", name, act, ob)
+
+ for (key, val, sub) in tokens:
+ if key == 'FCurve':
+ fcu = parseActionFCurve(act, ob, val, sub)
+ else:
+ defaultKey(key, val, sub, 'act', [], globals(), locals())
+ ob.animation_data.action = None
+ bpy.ops.object.mode_set(mode='OBJECT')
+ return act
+
+def prepareActionFCurve(ob, created, args, tokens):
+ dataPath = args[0]
+ index = args[1]
+ (expr, channel) = channelFromDataPath(dataPath, index)
+ try:
+ if channel in created[expr]:
+ return
+ else:
+ created[expr].append(channel)
+ except:
+ created[expr] = [channel]
+
+ times = []
+ for (key, val, sub) in tokens:
+ if key == 'kp':
+ times.append(int(val[0]))
+
+ try:
+ data = eval(expr)
+ except:
+ print("Ignoring illegal expression: %s" % expr)
+ return
+
+ n = 0
+ for t in times:
+ #bpy.context.scene.current_frame = t
+ bpy.ops.anim.change_frame(frame = t)
+ try:
+ data.keyframe_insert(channel)
+ n += 1
+ except:
+ pass
+ #print("failed", data, expr, channel)
+ if n != len(times):
+ print("Mismatch", n, len(times), expr, channel)
+ return
+
+def channelFromDataPath(dataPath, index):
+ words = dataPath.split(']')
+ if len(words) == 1:
+ # location
+ expr = "ob"
+ channel = dataPath
+ elif len(words) == 2:
+ # pose.bones["tongue"].location
+ expr = "ob.%s]" % (words[0])
+ cwords = words[1].split('.')
+ channel = cwords[1]
+ elif len(words) == 3:
+ # pose.bones["brow.R"]["mad"]
+ expr = "ob.%s]" % (words[0])
+ cwords = words[1].split('"')
+ channel = cwords[1]
+ # print(expr, channel, index)
+ return (expr, channel)
+
+def parseActionFCurve(act, ob, args, tokens):
+ dataPath = args[0]
+ index = args[1]
+ (expr, channel) = channelFromDataPath(dataPath, index)
+ index = int(args[1])
+
+ success = False
+ for fcu in act.fcurves:
+ (expr1, channel1) = channelFromDataPath(fcu.data_path, fcu.array_index)
+ if expr1 == expr and channel1 == channel and fcu.array_index == index:
+ success = True
+ break
+ if not success:
+ return None
+
+ n = 0
+ for (key, val, sub) in tokens:
+ if key == 'kp':
+ try:
+ pt = fcu.keyframe_points[n]
+ pt.interpolation = 'LINEAR'
+ pt = parseKeyFramePoint(pt, val, sub)
+ n += 1
+ except:
+ pass
+ #print(tokens)
+ #raise NameError("kp", fcu, n, len(fcu.keyframe_points), val)
+ else:
+ defaultKey(key, val, sub, 'fcu', [], globals(), locals())
+ return fcu
+
+def parseKeyFramePoint(pt, args, tokens):
+ pt.co = (float(args[0]), float(args[1]))
+ if len(args) > 2:
+ pt.handle1 = (float(args[2]), float(args[3]))
+ pt.handle2 = (float(args[3]), float(args[5]))
+ return pt
+
+#
+# parseAnimationData(rna, tokens):
+# parseDriver(drv, args, tokens):
+# parseDriverVariable(var, args, tokens):
+#
+
+def parseAnimationData(rna, tokens):
+ if 0 and toggle & T_MHX:
+ return
+ if rna.animation_data == None:
+ rna.animation_data_create()
+ adata = rna.animation_data
+ for (key, val, sub) in tokens:
+ if key == 'FCurve':
+ fcu = parseAnimDataFCurve(adata, rna, val, sub)
+ else:
+ defaultKey(key, val, sub, 'adata', [], globals(), locals())
+ return adata
+
+def parseAnimDataFCurve(adata, rna, args, tokens):
+ if invalid(args[2]):
+ return
+ dataPath = args[0]
+ index = int(args[1])
+ # print("parseAnimDataFCurve", adata, dataPath, index)
+ for (key, val, sub) in tokens:
+ if key == 'Driver':
+ fcu = parseDriver(adata, dataPath, index, rna, val, sub)
+ elif key == 'FModifier':
+ parseFModifier(fcu, val, sub)
+ else:
+ defaultKey(key, val, sub, 'fcu', [], globals(), locals())
+ return fcu
+
+"""
+ fcurve = con.driver_add("influence", 0)
+ driver = fcurve.driver
+ driver.type = 'AVERAGE'
+"""
+def parseDriver(adata, dataPath, index, rna, args, tokens):
+ if dataPath[-1] == ']':
+ words = dataPath.split(']')
+ expr = "rna." + words[0] + ']'
+ pwords = words[1].split('"')
+ prop = pwords[1]
+ # print("prop", expr, prop)
+ bone = eval(expr)
+ return None
+ else:
+ words = dataPath.split('.')
+ channel = words[-1]
+ expr = "rna"
+ for n in range(len(words)-1):
+ expr += "." + words[n]
+ expr += ".driver_add('%s', index)" % channel
+
+ # print("expr", rna, expr)
+ fcu = eval(expr)
+ drv = fcu.driver
+ drv.type = args[0]
+ for (key, val, sub) in tokens:
+ if key == 'DriverVariable':
+ var = parseDriverVariable(drv, rna, val, sub)
+ else:
+ defaultKey(key, val, sub, 'drv', [], globals(), locals())
+ return fcu
+
+def parseDriverVariable(drv, rna, args, tokens):
+ var = drv.variables.new()
+ var.name = args[0]
+ var.type = args[1]
+ nTarget = 0
+ # print("var", var, var.name, var.type)
+ for (key, val, sub) in tokens:
+ if key == 'Target':
+ parseDriverTarget(var, nTarget, rna, val, sub)
+ nTarget += 1
+ else:
+ defaultKey(key, val, sub, 'var', [], globals(), locals())
+ return var
+
+def parseFModifier(fcu, args, tokens):
+ #fmod = fcu.modifiers.new()
+ fmod = fcu.modifiers[0]
+ #fmod.type = args[0]
+ #print("fmod", fmod, fmod.type)
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, 'fmod', [], globals(), locals())
+ return fmod
+
+"""
+ var = driver.variables.new()
+ var.name = target_bone
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = obj
+ var.targets[0].rna_path = driver_path
+"""
+def parseDriverTarget(var, nTarget, rna, args, tokens):
+ targ = var.targets[nTarget]
+ # targ.rna_path = args[0]
+ # targ.id_type = args[1]
+ targ.id = loadedData['Object'][args[0]]
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, 'targ', [], globals(), locals())
+ #print("Targ", targ, targ.id, targ.data_path, targ.id_type, targ.bone_target, targ.use_local_space_transforms)
+ return targ
+
+
+#
+# parseMaterial(args, ext, tokens):
+# parseMTex(mat, args, tokens):
+# parseTexture(args, tokens):
+#
+
+def parseMaterial(args, tokens):
+ global todo
+ name = args[0]
+ #print("Parse material "+name)
+ mat = bpy.data.materials.new(name)
+ if mat == None:
+ return None
+ loadedData['Material'][name] = mat
+ #print("Material %s %s %s" % (mat, name, loadedData['Material'][name]))
+ for (key, val, sub) in tokens:
+ if key == 'MTex':
+ parseMTex(mat, val, sub)
+ elif key == 'Ramp':
+ parseRamp(mat, val, sub)
+ elif key == 'SSS':
+ parseSSS(mat, val, sub)
+ elif key == 'Strand':
+ parseStrand(mat, val, sub)
+ else:
+ exclude = ['specular_intensity', 'tangent_shading']
+ defaultKey(key, val, sub, 'mat', [], globals(), locals())
+ #print("Done ", mat)
+
+ return mat
+
+def parseMTex(mat, args, tokens):
+ global todo
+ index = int(args[0])
+ texname = args[1]
+ texco = args[2]
+ mapto = args[3]
+
+ mat.add_texture(texture = loadedData['Texture'][texname], texture_coordinates = texco, map_to = mapto)
+ mtex = mat.texture_slots[index]
+ #mat.use_textures[index] = Bool(use)
+
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, "mtex", [], globals(), locals())
+
+ return mtex
+
+def parseTexture(args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing texture %s" % args )
+ name = args[0]
+ tex = bpy.data.textures.new(name)
+ typ = args[1]
+ tex.type = typ
+ tex = tex.recast_type()
+ loadedData['Texture'][name] = tex
+
+ for (key, val, sub) in tokens:
+ if key == 'Image':
+ try:
+ imgName = val[0]
+ img = loadedData['Image'][imgName]
+ tex.image = img
+ except:
+ msg = "Unable to load image '%s'" % val[0]
+ elif key == 'Ramp':
+ parseRamp(tex, val, sub)
+ else:
+ defaultKey(key, val, sub, "tex", ['use_nodes', 'use_textures', 'contrast'], globals(), locals())
+
+ return tex
+
+def parseRamp(data, args, tokens):
+ nvar = "data.%s" % args[0]
+ use = "data.use_%s = True" % args[0]
+ exec(use)
+ ramp = eval(nvar)
+ elts = ramp.elements
+ n = 0
+ for (key, val, sub) in tokens:
+ # print("Ramp", key, val)
+ if key == 'Element':
+ elts[n].color = eval(val[0])
+ elts[n].position = eval(val[1])
+ n += 1
+ else:
+ defaultKey(key, val, sub, "tex", ['use_nodes', 'use_textures', 'contrast'], globals(), locals())
+
+def parseSSS(mat, args, tokens):
+ sss = mat.subsurface_scattering
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, "sss", [], globals(), locals())
+
+def parseStrand(mat, args, tokens):
+ strand = mat.strand
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, "strand", [], globals(), locals())
+
+#
+# doLoadImage(filepath):
+# loadImage(filepath):
+# parseImage(args, tokens):
+#
+
+def doLoadImage(filepath):
+ path1 = os.path.expanduser(filepath)
+ file1 = os.path.realpath(path1)
+ if os.path.isfile(file1):
+ print( "Found file "+file1 )
+ try:
+ img = bpy.data.images.load(file1)
+ return img
+ except:
+ print( "Cannot read image" )
+ return None
+ else:
+ print( "No file "+file1 )
+ return None
+
+
+def loadImage(filepath):
+ global TexDir, warnedTextureDir, loadedData
+
+ texDir = os.path.expanduser(TexDir)
+ path1 = os.path.expanduser(filepath)
+ file1 = os.path.realpath(path1)
+ (path, filename) = os.path.split(file1)
+ (name, ext) = os.path.splitext(filename)
+ print( "Loading ", filepath, " = ", filename )
+
+ # img = doLoadImage(texDir+"/"+name+".png")
+ # if img:
+ # return img
+
+ img = doLoadImage(texDir+"/"+filename)
+ if img:
+ return img
+
+ # img = doLoadImage(path+"/"+name+".png")
+ # if img:
+ # return img
+
+ img = doLoadImage(path+"/"+filename)
+ if img:
+ return img
+
+ if warnedTextureDir:
+ return None
+ warnedTextureDir = True
+ return None
+ TexDir = Draw.PupStrInput("TexDir? ", path, 100)
+
+ texDir = os.path.expanduser(TexDir)
+ img = doLoadImage(texDir+"/"+name+".png")
+ if img:
+ return img
+
+ img = doLoadImage(TexDir+"/"+filename)
+ return img
+
+def parseImage(args, tokens):
+ global todo
+ imgName = args[0]
+ img = None
+ for (key, val, sub) in tokens:
+ if key == 'Filename':
+ filename = val[0]
+ for n in range(1,len(val)):
+ filename += " " + val[n]
+ img = loadImage(filename)
+ if img == None:
+ return None
+ img.name = imgName
+ else:
+ defaultKey(key, val, sub, "img", ['depth', 'dirty', 'has_data', 'size', 'type'], globals(), locals())
+ print ("Image %s" % img )
+ loadedData['Image'][imgName] = img
+ return img
+
+#
+# parseObject(args, tokens):
+# createObject(type, name, data, datName):
+# createObjectAndData(args, typ):
+#
+
+def parseObject(args, tokens):
+ if verbosity > 2:
+ print( "Parsing object %s" % args )
+ name = args[0]
+ typ = args[1]
+ datName = args[2]
+ try:
+ data = loadedData[typ.capitalize()][datName]
+ except:
+ data = None
+
+ if data == None and typ != 'EMPTY':
+ print("Failed to find data: %s %s %s" % (name, typ, datName))
+ return
+
+ try:
+ ob = loadedData['Object'][name]
+ bpy.context.scene.objects.active = ob
+ #print("Found data")
+ except:
+ ob = createObject(typ, name, data, datName)
+ if bpy.context.object != ob:
+ print("Context", ob, bpy.context.object, bpy.context.scene.objects.active)
+ # ob = foo
+
+ for (key, val, sub) in tokens:
+ if key == 'Modifier':
+ parseModifier(ob, val, sub)
+ elif key == 'Constraint':
+ parseConstraint(ob.constraints, val, sub)
+ elif key == 'AnimationData':
+ if eval(val[0]):
+ parseAnimationData(ob, sub)
+ elif key == 'ParticleSystem':
+ parseParticleSystem(ob, val, sub)
+ else:
+ defaultKey(key, val, sub, "ob", ['type', 'data'], globals(), locals())
+
+ # Needed for updating layers
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ return
+
+def createObject(typ, name, data, datName):
+ #print( "Creating object %s %s %s" % (typ, name, data) )
+ ob = bpy.data.objects.new(name, data)
+ loadedData[typ][datName] = data
+ loadedData['Object'][name] = ob
+ return ob
+
+def linkObject(ob, data):
+ #print("Data", data, ob.data)
+ if data and ob.data == None:
+ ob.data = data
+ scn = bpy.context.scene
+ scn.objects.link(ob)
+ scn.objects.active = ob
+ #print("Linked object", ob)
+ #print("Scene", scn)
+ #print("Active", scn.objects.active)
+ #print("Context", bpy.context.object)
+ return ob
+
+def createObjectAndData(args, typ):
+ datName = args[0]
+ obName = args[1]
+ bpy.ops.object.add(type=typ.upper())
+ ob = bpy.context.object
+ ob.name = obName
+ ob.data.name = datName
+ loadedData[typ][datName] = ob.data
+ loadedData['Object'][obName] = ob
+ return ob.data
+
+
+#
+# parseModifier(ob, args, tokens):
+#
+
+def parseModifier(ob, args, tokens):
+ name = args[0]
+ typ = args[1]
+ if typ == 'PARTICLE_SYSTEM':
+ return None
+ mod = ob.modifiers.new(name, typ)
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, 'mod', [], globals(), locals())
+ return mod
+
+#
+# parseParticleSystem(ob, args, tokens):
+# parseParticles(particles, args, tokens):
+# parseParticle(par, args, tokens):
+#
+
+def parseParticleSystem(ob, args, tokens):
+ print(ob, bpy.context.object)
+ pss = ob.particle_systems
+ print(pss, pss.values())
+ name = args[0]
+ typ = args[1]
+ #psys = pss.new(name, typ)
+ bpy.ops.object.particle_system_add()
+ print(pss, pss.values())
+ psys = pss[-1]
+ psys.name = name
+ psys.settings.type = typ
+ loadedData['ParticleSystem'][name] = psys
+ print("Psys", psys)
+
+ for (key, val, sub) in tokens:
+ if key == 'Particles':
+ parseParticles(psys, val, sub)
+ else:
+ defaultKey(key, val, sub, 'psys', [], globals(), locals())
+ return psys
+
+def parseParticles(psys, args, tokens):
+ particles = psys.particles
+ bpy.ops.particle.particle_edit_toggle()
+ n = 0
+ for (key, val, sub) in tokens:
+ if key == 'Particle':
+ parseParticle(particles[n], val, sub)
+ n += 1
+ else:
+ for par in particles:
+ defaultKey(key, val, sub, 'par', [], globals(), locals())
+ bpy.ops.particle.particle_edit_toggle()
+ return particles
+
+def parseParticle(par, args, tokens):
+ n = 0
+ for (key, val, sub) in tokens:
+ if key == 'h':
+ h = par.hair[n]
+ h.location = eval(val[0])
+ h.time = int(val[1])
+ h.weight = float(val[2])
+ n += 1
+ elif key == 'location':
+ par.location = eval(val[0])
+ return
+
+#
+# unpackList(list_of_tuples):
+#
+
+def unpackList(list_of_tuples):
+ l = []
+ for t in list_of_tuples:
+ l.extend(t)
+ return l
+
+#
+# parseMesh (args, tokens):
+#
+
+def parseMesh (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing mesh %s" % args )
+
+ mename = args[0]
+ obname = args[1]
+ me = bpy.data.meshes.new(mename)
+ ob = createObject('Mesh', obname, me, mename)
+
+ verts = []
+ edges = []
+ faces = []
+ vertsTex = []
+ texFaces = []
+
+ for (key, val, sub) in tokens:
+ if key == 'Verts':
+ verts = parseVerts(sub)
+ elif key == 'Edges':
+ edges = parseEdges(sub)
+ elif key == 'Faces':
+ faces = parseFaces(sub)
+
+ if faces:
+ #x = me.from_pydata(verts, [], faces)
+ me.add_geometry(len(verts), 0, len(faces))
+ me.verts.foreach_set("co", unpackList(verts))
+ me.faces.foreach_set("verts_raw", unpackList(faces))
+ else:
+ #x = me.from_pydata(verts, edges, [])
+ me.add_geometry(len(verts), len(edges), 0)
+ me.verts.foreach_set("co", unpackList(verts))
+ me.edges.foreach_set("verts", unpackList(edges))
+ #print(x)
+ me.update()
+ #print(me)
+ linkObject(ob, me)
+
+ mats = []
+ for (key, val, sub) in tokens:
+ if key == 'Verts' or \
+ key == 'Edges':
+ pass
+ elif key == 'Faces':
+ parseFaces2(sub, me)
+ elif key == 'MeshTextureFaceLayer':
+ parseUvTexture(val, sub, me)
+ elif key == 'MeshColorLayer':
+ parseVertColorLayer(val, sub, me)
+ elif key == 'VertexGroup':
+ parseVertexGroup(ob, me, val, sub)
+ elif key == 'ShapeKeys':
+ parseShapeKeys(ob, me, val, sub)
+ elif key == 'Material':
+ try:
+ me.add_material(loadedData['Material'][val[0]])
+ except:
+ print("Could not add material", val[0])
+ else:
+ defaultKey(key, val, sub, "me", [], globals(), locals())
+
+ return me
+
+#
+# parseVerts(tokens):
+# parseEdges(tokens):
+# parseFaces(tokens):
+# parseFaces2(tokens, me):
+#
+
+def parseVerts(tokens):
+ verts = []
+ for (key, val, sub) in tokens:
+ if key == 'v':
+ verts.append( (float(val[0]), float(val[1]), float(val[2])) )
+ return verts
+
+def parseEdges(tokens):
+ edges = []
+ for (key, val, sub) in tokens:
+ if key == 'e':
+ edges.append((int(val[0]), int(val[1])))
+ return edges
+
+def parseFaces(tokens):
+ faces = []
+ for (key, val, sub) in tokens:
+ if key == 'f':
+ if len(val) == 3:
+ face = [int(val[0]), int(val[1]), int(val[2]), 0]
+ elif len(val) == 4:
+ face = [int(val[0]), int(val[1]), int(val[2]), int(val[3])]
+ faces.append(face)
+ return faces
+
+def parseFaces2(tokens, me):
+ n = 0
+ for (key, val, sub) in tokens:
+ if key == 'ft':
+ f = me.faces[n]
+ f.material_index = int(val[0])
+ f.smooth = int(val[1])
+ n += 1
+ elif key == 'ftall':
+ mat = int(val[0])
+ smooth = int(val[1])
+ for f in me.faces:
+ f.material_index = mat
+ f.smooth = smooth
+ return
+
+
+#
+# parseUvTexture(args, tokens, me):
+# parseUvTexData(args, tokens, uvdata):
+#
+
+def parseUvTexture(args, tokens, me):
+ me.add_uv_texture()
+ uvtex = me.uv_textures[-1]
+ name = args[0]
+ uvtex.name = name
+ loadedData['MeshTextureFaceLayer'][name] = uvtex
+ for (key, val, sub) in tokens:
+ if key == 'Data':
+ parseUvTexData(val, sub, uvtex.data)
+ else:
+ defaultKey(key, val, sub, "uvtex", [], globals(), locals())
+ return
+
+def parseUvTexData(args, tokens, data):
+ n = 0
+ for (key, val, sub) in tokens:
+ if key == 'vt':
+ data[n].uv1 = (float(val[0]), float(val[1]))
+ data[n].uv2 = (float(val[2]), float(val[3]))
+ data[n].uv3 = (float(val[4]), float(val[5]))
+ if len(val) > 6:
+ data[n].uv4 = (float(val[6]), float(val[7]))
+ n += 1
+ else:
+ pass
+ #for i in range(n):
+ # defaultKey(key, val, sub, "data[i]", [], globals(), locals())
+ return
+
+#
+# parseVertColorLayer(args, tokens, me):
+# parseVertColorData(args, tokens, data):
+#
+
+def parseVertColorLayer(args, tokens, me):
+ name = args[0]
+ print("VertColorLayer", name)
+ me.add_vertex_color()
+ vcol = me.vertex_colors[-1]
+ vcol.name = name
+ loadedData['MeshColorLayer'][name] = vcol
+ for (key, val, sub) in tokens:
+ if key == 'Data':
+ parseVertColorData(val, sub, vcol.data)
+ else:
+ defaultKey(key, val, sub, "vcol", [], globals(), locals())
+ return
+
+def parseVertColorData(args, tokens, data):
+ n = 0
+ for (key, val, sub) in tokens:
+ if key == 'cv':
+ data[n].color1 = eval(val[0])
+ data[n].color2 = eval(val[1])
+ data[n].color3 = eval(val[2])
+ data[n].color4 = eval(val[3])
+ n += 1
+ return
+
+
+#
+# parseVertexGroup(ob, me, args, tokens):
+#
+
+def parseVertexGroup(ob, me, args, tokens):
+ global toggle
+ if verbosity > 2:
+ print( "Parsing vertgroup %s" % args )
+ grpName = args[0]
+ try:
+ res = eval(args[1])
+ except:
+ res = True
+ if not res:
+ return
+
+ if (toggle & T_Armature) or (grpName in ['Eye_L', 'Eye_R', 'Gums', 'Head', 'Jaw', 'Left', 'Middle', 'Right', 'Scalp']):
+ group = ob.add_vertex_group(grpName)
+ group.name = grpName
+ loadedData['VertexGroup'][grpName] = group
+ for (key, val, sub) in tokens:
+ if key == 'wv':
+ ob.add_vertex_to_group( int(val[0]), group, float(val[1]), 'REPLACE')
+ return
+
+
+#
+# parseShapeKeys(ob, me, args, tokens):
+# parseShapeKey(ob, me, args, tokens):
+# addShapeKey(ob, name, vgroup, tokens):
+# doShape(name):
+#
+
+def doShape(name):
+ if (toggle & T_Shape+T_Face) and (name == 'Basis'):
+ return True
+ else:
+ return (toggle & T_Face)
+
+def parseShapeKeys(ob, me, args, tokens):
+ if bpy.context.object == None:
+ return
+ for (key, val, sub) in tokens:
+ if key == 'ShapeKey':
+ parseShapeKey(ob, me, val, sub)
+ elif key == 'AnimationData':
+ if me.shape_keys:
+ parseAnimationData(me.shape_keys, sub)
+ return
+
+
+def parseShapeKey(ob, me, args, tokens):
+ if verbosity > 0:
+ print( "Parsing ob %s shape %s" % (bpy.context.object, args[0] ))
+ name = args[0]
+ lr = args[1]
+ if invalid(args[2]):
+ return
+
+ if lr == 'Sym' or toggle & T_Symm:
+ addShapeKey(ob, name, None, tokens)
+ elif lr == 'LR':
+ addShapeKey(ob, name+'_L', 'Left', tokens)
+ addShapeKey(ob, name+'_R', 'Right', tokens)
+ else:
+ raise NameError("ShapeKey L/R %s" % lr)
+ return
+
+def addShapeKey(ob, name, vgroup, tokens):
+ bpy.ops.object.shape_key_add(False)
+ skey = ob.active_shape_key
+ if name != 'Basis':
+ skey.relative_key = loadedData['ShapeKey']['Basis']
+ skey.name = name
+ if vgroup:
+ skey.vertex_group = vgroup
+ loadedData['ShapeKey'][name] = skey
+
+ for (key, val, sub) in tokens:
+ if key == 'sv':
+ index = int(val[0])
+ pt = skey.data[index].co
+ pt[0] += float(val[1])
+ pt[1] += float(val[2])
+ pt[2] += float(val[3])
+ else:
+ defaultKey(key, val, sub, "skey", [], globals(), locals())
+
+ return
+
+
+#
+# parseArmature (obName, args, tokens)
+#
+
+def parseArmature (args, tokens):
+ global toggle, theScale
+ if verbosity > 2:
+ print( "Parsing armature %s" % args )
+
+ amtname = args[0]
+ obname = args[1]
+ mode = args[2]
+
+ if mode == 'Rigify':
+ toggle |= T_Rigify
+ theScale = 0.1
+ return parseRigify(amtname, obname, tokens)
+
+ toggle &= ~T_Rigify
+ theScale = 1.0
+ amt = bpy.data.armatures.new(amtname)
+ ob = createObject('Armature', obname, amt, amtname)
+
+ linkObject(ob, amt)
+ print("Linked")
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ heads = {}
+ tails = {}
+ for (key, val, sub) in tokens:
+ if key == 'Bone':
+ bname = val[0]
+ if not invalid(val[1]):
+ bone = amt.edit_bones.new(bname)
+ parseBone(bone, amt.edit_bones, sub, heads, tails)
+ loadedData['Bone'][bname] = bone
+ else:
+ defaultKey(key, val, sub, "amt", ['MetaRig'], globals(), locals())
+ bpy.ops.object.mode_set(mode='OBJECT')
+ return amt
+
+#
+# parseRigify(amtname, obname, tokens):
+#
+
+def parseRigify(amtname, obname, tokens):
+ (key,val,sub) = tokens[0]
+ if key != 'MetaRig':
+ raise NameError("Expected MetaRig")
+ typ = val[0]
+ if typ == "human":
+ bpy.ops.object.armature_human_advanced_add()
+ else:
+ bpy.ops.pose.metarig_sample_add(type = typ)
+ ob = bpy.context.scene.objects.active
+ amt = ob.data
+ loadedData['Rigify'][obname] = ob
+ loadedData['Armature'][amtname] = amt
+ loadedData['Object'][obname] = ob
+ print("Rigify object", ob, amt)
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ heads = {}
+ tails = {}
+ for (bname, bone) in amt.edit_bones.items():
+ heads[bname] = 10*theScale*bone.head
+ tails[bname] = 10*theScale*bone.tail
+
+ for (key, val, sub) in tokens:
+ if key == 'Bone':
+ bname = val[0]
+ print("Bone", bname)
+ try:
+ bone = amt.edit_bones[bname]
+ except:
+ print("Did not find bone %s" % bname)
+ bone = None
+ print(" -> ", bone)
+ if bone:
+ parseBone(bone, amt.edit_bones, sub, heads, tails)
+ else:
+ defaultKey(key, val, sub, "amt", ['MetaRig'], globals(), locals())
+ bpy.ops.object.mode_set(mode='OBJECT')
+ return amt
+
+#
+# parseBone(bone, bones, tokens, heads, tails):
+#
+
+def parseBone(bone, bones, tokens, heads, tails):
+ global todo
+
+ for (key, val, sub) in tokens:
+ if key == "head":
+ bone.head = (float(val[0]), float(val[1]), float(val[2]))
+ elif key == "tail":
+ bone.tail = (float(val[0]), float(val[1]), float(val[2]))
+ elif key == "head-as":
+ target = val[0]
+ if val[1] == 'head':
+ bone.head = heads[bone.name] + bones[target].head - heads[target]
+ elif val[1] == 'tail':
+ bone.head = heads[bone.name] + bones[target].tail - tails[target]
+ else:
+ raise NameError("head-as %s" % val)
+ elif key == "tail-as":
+ target = val[0]
+ if val[1] == 'head':
+ bone.tail = tails[bone.name] + bones[target].head - heads[target]
+ elif val[1] == 'tail':
+ bone.tail = tails[bone.name] + bones[target].tail - tails[target]
+ else:
+ raise NameError("tail-as %s" % val)
+ elif key == 'hide_select':
+ pass
+ else:
+ defaultKey(key, val, sub, "bone", [], globals(), locals())
+
+ return bone
+
+#
+# parsePose (args, tokens):
+#
+
+def parsePose (args, tokens):
+ global todo
+ if toggle & T_Rigify:
+ return
+ name = args[0]
+ ob = loadedData['Object'][name]
+ bpy.context.scene.objects.active = ob
+ bpy.ops.object.mode_set(mode='POSE')
+ pbones = ob.pose.bones
+ nGrps = 0
+ for (key, val, sub) in tokens:
+ if key == 'Posebone':
+ parsePoseBone(pbones, ob, val, sub)
+ elif key == 'BoneGroup':
+ parseBoneGroup(ob.pose, nGrps, val, sub)
+ nGrps += 1
+ elif key == 'SetProp':
+ bone = val[0]
+ prop = val[1]
+ value = eval(val[2])
+ pb = pbones[bone]
+ print("Setting", pb, prop, val)
+ pb[prop] = value
+ print("Prop set", pb[prop])
+ else:
+ defaultKey(key, val, sub, "ob.pose", [], globals(), locals())
+ bpy.ops.object.mode_set(mode='OBJECT')
+ return ob
+
+
+#
+# parsePoseBone(pbones, args, tokens):
+# parseArray(data, exts, args):
+#
+
+def parseBoneGroup(pose, nGrps, args, tokens):
+ global todo
+ return
+ print( "Parsing bonegroup %s" % args )
+ name = args[0]
+ print(dir(pose.bone_groups))
+ bg = pose.bone_groups.add()
+ print("Created", bg)
+ loadedData['BoneGroup'][name] = bg
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, "bg", [], globals(), locals())
+ return
+
+def parsePoseBone(pbones, ob, args, tokens):
+ global todo
+ #print( "Parsing posebone %s" % args )
+ if invalid(args[1]):
+ return
+ name = args[0]
+ pb = pbones[name]
+
+ # Make posebone active - don't know how to do this in pose mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+ ob.data.bones.active = pb.bone
+ bpy.ops.object.mode_set(mode='POSE')
+
+ for (key, val, sub) in tokens:
+ if key == 'Constraint':
+ cns = parseConstraint(pb.constraints, val, sub)
+ elif key == 'bpyops':
+ expr = "bpy.ops.%s" % val[0]
+ print(expr)
+ print("ob", bpy.context.active_object)
+ print("b", bpy.context.active_bone)
+ print("pb", bpy.context.active_pose_bone)
+ print("md", bpy.context.mode)
+ exec(expr)
+ print("alive")
+ elif key == 'ik_dof':
+ parseArray(pb, ["ik_dof_x", "ik_dof_y", "ik_dof_z"], val)
+ elif key == 'ik_limit':
+ parseArray(pb, ["ik_limit_x", "ik_limit_y", "ik_limit_z"], val)
+ elif key == 'ik_max':
+ parseArray(pb, ["ik_max_x", "ik_max_y", "ik_max_z"], val)
+ elif key == 'ik_min':
+ parseArray(pb, ["ik_min_x", "ik_min_y", "ik_min_z"], val)
+ elif key == 'ik_stiffness':
+ parseArray(pb, ["ik_stiffness_x", "ik_stiffness_y", "ik_stiffness_z"], val)
+ else:
+ defaultKey(key, val, sub, "pb", [], globals(), locals())
+ #print("pb %s done" % name)
+ return
+
+def parseArray(data, exts, args):
+ n = 1
+ for ext in exts:
+ expr = "data.%s = %s" % (ext, args[n])
+ # print(expr)
+ exec(expr)
+ n += 1
+ return
+
+#
+# parseConstraint(constraints, args, tokens)
+#
+
+def parseConstraint(constraints, args, tokens):
+ if invalid(args[2]):
+ return None
+ cns = constraints.new(args[1])
+ #bpy.ops.pose.constraint_add(type=args[1])
+ #cns = pb.constraints[-1]
+
+ cns.name = args[0]
+ #print("cns", cns.name)
+ for (key,val,sub) in tokens:
+ if key == 'invert':
+ parseArray(cns, ["invert_x", "invert_y", "invert_z"], val)
+ elif key == 'use':
+ parseArray(cns, ["use_x", "use_y", "use_z"], val)
+ elif key == 'pos_lock':
+ parseArray(cns, ["pos_lock_x", "pos_lock_y", "pos_lock_z"], val)
+ elif key == 'rot_lock':
+ parseArray(cns, ["rot_lock_x", "rot_lock_y", "rot_lock_z"], val)
+ else:
+ defaultKey(key, val, sub, "cns", [], globals(), locals())
+ #print("cns %s done" % cns.name)
+ return cns
+
+def insertInfluenceIpo(cns, bone):
+ global todo
+ if bone != 'PArmIK_L' and bone != 'PArmIK_R' and bone != 'PLegIK_L' and bone != 'PLegIK_R':
+ return False
+
+ if (toggle & T_FKIK):
+ fcurve = cns.driver_add("influence", 0)
+ fcurve.driver.type = 'AVERAGE'
+
+ var = fcurve.driver.variables.new()
+ var.name = bone
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = getObject('HumanRig', 'var.targets[0].id', globals(), locals())
+ var.targets[0].bone_target = bone
+ var.targets[0].transform_type = 'LOC_X'
+ # controller_path = fk_chain.arm_p.path_to_id()
+ #var.targets[0].data_path = controller_path + '["hinge"]'
+
+ mod = fcurve.modifiers[0]
+ mod.poly_order = 2
+ mod.coefficients[0] = 0.0
+ mod.coefficients[1] = 1.0
+ elif bone == 'PArmIK_L' or bone == 'PArmIK_R':
+ if toggle & T_ArmIK:
+ cns.influence = 1.0
+ else:
+ cns.influence = 0.0
+ elif bone == 'PLegIK_L' or bone == 'PLegIK_R':
+ if toggle & T_LegIK:
+ cns.influence = 1.0
+ else:
+ cns.influence = 0.0
+
+ return True
+
+#
+# parseCurve (args, tokens):
+# parseNurb(cu, nNurbs, args, tokens):
+# parseBezier(nurb, n, args, tokens):
+#
+
+def parseCurve (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing curve %s" % args )
+ cu = createObjectAndData(args, 'Curve')
+
+ nNurbs = 0
+ for (key, val, sub) in tokens:
+ if key == 'Nurb':
+ parseNurb(cu, nNurbs, val, sub)
+ nNurbs += 1
+ else:
+ defaultKey(key, val, sub, "cu", [], globals(), locals())
+ return
+
+def parseNurb(cu, nNurbs, args, tokens):
+ if nNurbs > 0:
+ bpy.ops.object.curve_add(type='BEZIER_CURVE')
+ print(cu.splines, list(cu.splines), nNurbs)
+ nurb = cu.splines[nNurbs]
+ nPoints = int(args[0])
+ print(nurb, nPoints)
+ for n in range(2, nPoints):
+ bpy.ops.curve.extrude(mode=1)
+
+ n = 0
+ for (key, val, sub) in tokens:
+ if key == 'bz':
+ parseBezier(nurb, n, val, sub)
+ n += 1
+ elif key == 'pt':
+ parsePoint(nurb, n, val, sub)
+ n += 1
+ else:
+ defaultKey(key, val, sub, "nurb", [], globals(), locals())
+ return
+
+def parseBezier(nurb, n, args, tokens):
+ bez = nurb[n]
+ bez.co = eval(args[0])
+ bez.handle1 = eval(args[1])
+ bez.handle1_type = args[2]
+ bez.handle2 = eval(args[3])
+ bez.handle2_type = args[4]
+ return
+
+def parsePoint(nurb, n, args, tokens):
+ pt = nurb[n]
+ pt.co = eval(args[0])
+ return
+
+#
+# parseLattice (args, tokens):
+#
+
+def parseLattice (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing lattice %s" % args )
+ lat = createObjectAndData(args, 'Lattice')
+ for (key, val, sub) in tokens:
+ if key == 'Points':
+ parseLatticePoints(val, sub, lat.points)
+ else:
+ defaultKey(key, val, sub, "lat", [], globals(), locals())
+ return
+
+def parseLatticePoints(args, tokens, points):
+ global todo
+ n = 0
+ for (key, val, sub) in tokens:
+ if key == 'pt':
+ v = points[n].co
+ (x,y,z) = eval(val[0])
+ v.x = x
+ v.y = y
+ v.z = z
+
+ v = points[n].deformed_co
+ (x,y,z) = eval(val[1])
+ v.x = x
+ v.y = y
+ v.z = z
+
+ n += 1
+ return
+
+#
+# parseGroup (args, tokens):
+#
+
+def parseGroup (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing group %s" % args )
+
+ grpName = args[0]
+ grp = bpy.data.groups.new(grpName)
+ loadedData['Group'][grpName] = grp
+ for (key, val, sub) in tokens:
+ if key == 'Objects':
+ parseGroupObjects(val, sub, grp)
+ else:
+ defaultKey(key, val, sub, "grp", [], globals(), locals())
+ return
+
+def parseGroupObjects(args, tokens, grp):
+ global todo
+ for (key, val, sub) in tokens:
+ if key == 'ob':
+ try:
+ ob = loadedData['Object'][val[0]]
+ grp.objects.link(ob)
+ except:
+ pass
+ return
+
+#
+# postProcess()
+# setInfluence(bones, cnsName, w):
+#
+
+def postProcess():
+ if not toggle & T_MHX:
+ return
+ if toggle & T_Rigify:
+ return
+ for rig in loadedData['Rigify'].values():
+ bpy.context.scene.objects.active = rig
+ print("Rigify", rig)
+ bpy.ops.pose.metarig_generate()
+ print("Metarig generated")
+ #bpy.context.scene.objects.unlink(rig)
+ rig = bpy.context.scene.objects.active
+ print("Rigged", rig, bpy.context.object)
+ ob = loadedData['Object']['Human']
+ mod = ob.modifiers[0]
+ print(ob, mod, mod.object)
+ mod.object = rig
+ print("Rig changed", mod.object)
+ return
+
+#
+# parseProcess(args, tokens):
+#
+
+def parseProcess(args, tokens):
+ return
+ rig = loadedData['Object'][args[0]]
+ parents = {}
+ objects = []
+
+ for (key, val, sub) in tokens:
+ if key == 'Reparent':
+ bname = val[0]
+ try:
+ eb = ebones[bname]
+ parents[bname] = eb.parent.name
+ eb.parent = ebones[val[1]]
+ except:
+ pass
+ elif key == 'Bend':
+ print(val)
+ axis = val[1]
+ angle = float(val[2])
+ mat = mathutils.RotationMatrix(angle, 4, axis)
+ try:
+ pb = pbones[val[0]]
+ prod = pb.matrix_local * mat
+ for i in range(4):
+ for j in range(4):
+ pb.matrix_local[i][j] = prod[i][j]
+ print("Done", pb.matrix_local)
+ except:
+ pass
+ elif key == 'Pose':
+ bpy.context.scene.objects.active = rig
+ bpy.ops.object.mode_set(mode='POSE')
+ pbones = rig.pose.bones
+ elif key == 'Edit':
+ bpy.context.scene.objects.active = rig
+ bpy.ops.object.mode_set(mode='EDIT')
+ ebones = rig.data.edit_bones
+ elif key == 'Object':
+ bpy.ops.object.mode_set(mode='OBJECT')
+ try:
+ ob = loadedData['Object'][val[0]]
+ objects.append((ob,sub))
+ except:
+ ob = None
+ if ob:
+ bpy.context.scene.objects.active = ob
+ mod = ob.modifiers[0]
+ ob.modifiers.remove(mod)
+ for (key1, val1, sub1) in sub:
+ if key1 == 'Modifier':
+ parseModifier(ob, val1, sub1)
+
+ for (ob,tokens) in objects:
+ bpy.context.scene.objects.active = ob
+ bpy.ops.object.visual_transform_apply()
+ #print("vis", list(ob.modifiers))
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier='Armature')
+ #print("app", list(ob.modifiers))
+
+ bpy.context.scene.objects.active = rig
+ bpy.ops.object.mode_set(mode='POSE')
+ bpy.ops.pose.armature_apply()
+ bpy.ops.object.mode_set(mode='EDIT')
+ ebones = rig.data.edit_bones
+ for (bname, pname) in parents.items():
+ eb = ebones[bname]
+ par = ebones[pname]
+ if eb.connected:
+ par.tail = eb.head
+ eb.parent = par
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ for (ob,tokens) in objects:
+ bpy.context.scene.objects.active = ob
+ for (key, val, sub) in tokens:
+ if key == 'Modifier':
+ parseModifier(ob, val, sub)
+
+ return
+
+#
+# defaultKey(ext, args, tokens, var, exclude, glbals, lcals):
+#
+
+def defaultKey(ext, args, tokens, var, exclude, glbals, lcals):
+ global todo
+
+ if ext == 'Property':
+ expr = "%s['%s'] = %s" % (var, args[0], args[1])
+ print("Property", expr)
+ exec(expr, glbals, lcals)
+ #print("execd")
+ return
+
+ nvar = "%s.%s" % (var, ext)
+ # print(ext)
+ if ext in exclude:
+ return
+ #print("D", nvar)
+
+ if len(args) == 0:
+ raise NameError("Key length 0: %s" % ext)
+
+ rnaType = args[0]
+ if rnaType == 'Add':
+ print("*** Cannot Add yet ***")
+ return
+
+ elif rnaType == 'Refer':
+ typ = args[1]
+ name = args[2]
+ data = "loadedData['%s']['%s']" % (typ, name)
+
+ elif rnaType == 'Struct' or rnaType == 'Define':
+ typ = args[1]
+ name = args[2]
+ try:
+ data = eval(nvar, glbals, lcals)
+ except:
+ data = None
+ # print("Old structrna", nvar, data)
+
+ if data == None:
+ try:
+ creator = args[3]
+ except:
+ creator = None
+ # print("Creator", creator, eval(var,glbals,lcals))
+
+ try:
+ rna = eval(var,glbals,lcals)
+ data = eval(creator)
+ except:
+ data = None
+ # print("New struct", nvar, typ, data)
+
+ if rnaType == 'Define':
+ loadedData[typ][name] = data
+
+ if data:
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, "data", [], globals(), locals())
+
+ print("Struct done", nvar)
+ return
+
+ elif rnaType == 'PropertyRNA':
+ raise NameError("PropertyRNA!")
+ #print("PropertyRNA ", ext, var)
+ for (key, val, sub) in tokens:
+ defaultKey(ext, val, sub, nvar, [], glbals, lcals)
+ return
+
+ elif rnaType == 'Array':
+ for n in range(1, len(args)):
+ expr = "%s[%d] = %s" % (nvar, n-1, args[n])
+ exec(expr, glbals, lcals)
+ if len(args) > 0:
+ expr = "%s[0] = %s" % (nvar, args[1])
+ exec(expr, glbals, lcals)
+ return
+
+ elif rnaType == 'List':
+ data = []
+ for (key, val, sub) in tokens:
+ elt = eval(val[1], glbals, lcals)
+ data.append(elt)
+
+ elif rnaType == 'Matrix':
+ return
+ i = 0
+ n = len(tokens)
+ for (key, val, sub) in tokens:
+ if key == 'row':
+ for j in range(n):
+ expr = "%s[%d][%d] = %g" % (nvar, i, j, float(val[j]))
+ exec(expr, glbals, lcals)
+ i += 1
+ return
+
+ else:
+ try:
+ data = loadedData[rnaType][args[1]]
+ #print("From loaded", rnaType, args[1], data)
+ return data
+ except:
+ data = rnaType
+
+ #print(var, ext, data)
+ expr = "%s = %s" % (nvar, data)
+ try:
+ exec(expr, glbals, lcals)
+ except:
+ #print("Failed ",expr)
+ todo.append((expr, glbals, lcals))
+ return
+
+#
+# parseBoolArray(mask):
+#
+
+def parseBoolArray(mask):
+ list = []
+ for c in mask:
+ if c == '0':
+ list.append(False)
+ else:
+ list.append(True)
+ return list
+
+# parseMatrix(args, tokens)
+#
+
+def parseMatrix(args, tokens):
+ matrix = Matrix( [1,0,0,0], [0,1,0,0], [0,0,1,0], [0,0,0,1] )
+ i = 0
+ for (key, val, sub) in tokens:
+ if key == 'row':
+ matrix[i][0] = float(val[0])
+ matrix[i][1] = float(val[1])
+ matrix[i][2] = float(val[2])
+ matrix[i][3] = float(val[3])
+ i += 1
+ return matrix
+
+#
+# parseDefault(data, tokens, exclude):
+#
+
+def parseDefault(data, tokens):
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, "data", exclude, globals(), locals())
+
+
+#
+# Utilities
+#
+
+#
+# extractBpyType(data):
+#
+
+def extractBpyType(data):
+ typeSplit = str(type(data)).split("'")
+ if typeSplit[0] != '<class ':
+ return None
+ classSplit = typeSplit[1].split(".")
+ if classSplit[0] == 'bpy' and classSplit[1] == 'types':
+ return classSplit[2]
+ elif classSplit[0] == 'bpy_types':
+ return classSplit[1]
+ else:
+ return None
+
+#
+# Bool(string):
+#
+
+def Bool(string):
+ if string == 'True':
+ return True
+ elif string == 'False':
+ return False
+ else:
+ raise NameError("Bool %s?" % string)
+
+#
+# invalid(condition):
+#
+
+def invalid(condition):
+ global rigLeg, rigArm, toggle
+ res = eval(condition, globals())
+ try:
+ res = eval(condition, globals())
+ #print("%s = %s" % (condition, res))
+ return not res
+ except:
+ #print("%s invalid!" % condition)
+ return True
+
+#
+# clearScene(context):
+# hideLayers():
+#
+
+def clearScene():
+ global toggle
+ scn = bpy.context.scene
+ for n in range(len(scn.layers)):
+ scn.layers[n] = True
+ print("clearScene %s %s" % (toggle & T_Replace, scn))
+ if not toggle & T_Replace:
+ return scn
+
+ for ob in scn.objects:
+ if ob.type == "MESH" or ob.type == "ARMATURE":
+ scn.objects.active = ob
+ bpy.ops.object.mode_set(mode='OBJECT')
+ scn.objects.unlink(ob)
+ del ob
+ #print(scn.objects)
+ return scn
+
+def hideLayers():
+ scn = bpy.context.scene
+ for n in range(len(scn.layers)):
+ if n < 3:
+ scn.layers[n] = True
+ else:
+ scn.layers[n] = False
+ return
+
+#
+# User interface
+#
+
+DEBUG= False
+from bpy.props import *
+
+class IMPORT_OT_makehuman_mhx(bpy.types.Operator):
+ '''Import from MHX file format (.mhx)'''
+ bl_idname = "import_scene.makehuman_mhx"
+ bl_description = 'Import from MHX file format (.mhx)'
+ bl_label = "Import MHX"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+
+ filepath = StringProperty(name="File Path", description="Filepath used for importing the MHX file", maxlen= 1024, default= "")
+
+ #preset = BoolProperty(name="Use rig preset", description="Use rig preset (Classic/Gobo)?", default=True)
+ #presetRig = EnumProperty(name="Rig", description="Choose preset rig",
+ # items = [('Classic','Classic','Classic'), ('Gobo','Gobo','Gobo')], default = '1')
+ footRig = EnumProperty(name="Foot rig", description="Foot rig",
+ items = [('Reverse foot','Reverse foot','Reverse foot'), ('Gobo','Gobo','Gobo')], default = '1')
+ fingerRig = EnumProperty(name="Finger rig", description="Finger rig",
+ items = [('Panel','Panel','Panel'), ('Curl','Curl','Curl'), ('IK','IK','IK')], default = '1')
+
+ mesh = BoolProperty(name="Mesh", description="Use main mesh", default=toggle&T_Mesh)
+ armature = BoolProperty(name="Armature", description="Use armature", default=toggle&T_Armature)
+ proxy = BoolProperty(name="Proxy", description="Use proxy object", default=toggle&T_Proxy)
+ replace = BoolProperty(name="Replace scene", description="Replace scene", default=toggle&T_Replace)
+ face = BoolProperty(name="Face shapes", description="Include facial shapekeys", default=toggle&T_Face)
+ shape = BoolProperty(name="Body shapes", description="Include body shapekeys", default=toggle&T_Shape)
+ symm = BoolProperty(name="Symmetric shapes", description="Keep shapekeys symmetric", default=toggle&T_Symm)
+
+ def execute(self, context):
+ global toggle
+ O_Mesh = T_Mesh if self.properties.mesh else 0
+ O_Armature = T_Armature if self.properties.armature else 0
+ O_Proxy = T_Proxy if self.properties.proxy else 0
+ O_Replace = T_Replace if self.properties.replace else 0
+ O_Face = T_Face if self.properties.face else 0
+ O_Shape = T_Shape if self.properties.shape else 0
+ O_Symm = T_Symm if self.properties.symm else 0
+ #O_Preset = T_Preset if self.properties.preset else 0
+ toggle = O_Mesh | O_Armature | O_Proxy | T_ArmIK | T_LegIK | O_Replace | O_Face | O_Shape | O_Symm | T_MHX
+
+
+ readMhxFile(self.properties.filepath,
+ (self.properties.footRig,
+ self.properties.fingerRig))
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.manager
+ wm.add_fileselect(self)
+ return {'RUNNING_MODAL'}
+
+'''
+class MakeHumanFKIKPanel(bpy.types.Panel):
+ bl_label = "MakeHuman FK/IK"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+
+ def draw(self, context):
+ layout = self.layout
+ ob = bpy.context.active_object
+ if ob.type == 'ARMATURE':
+ layout.row().prop(ob, "PArmIK_L")
+ layout.row().prop(ob, "PArmIK_R")
+ layout.row().prop(ob, "PLegIK_L")
+ layout.row().prop(ob, "PLegIK_R")
+
+ layout.row().prop(ob, "PHandLocal_L")
+ layout.row().prop(ob, "PHandLocal_R")
+ layout.row().prop(ob, "PFootLocal_L")
+ layout.row().prop(ob, "PFootLocal_R")
+ return
+
+class MakeHumanFingerPanel(bpy.types.Panel):
+ bl_label = "MakeHuman Fingers"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "UI"
+
+ def draw(self, context):
+ layout = self.layout
+ pb = bpy.context.active_pose_bone
+ layout.row().prop(pb, "MHRelax")
+ layout.row().prop(pb, "MHCurl")
+ layout.row().prop(pb, "MHCone")
+ layout.row().prop(pb, "MHSpread")
+ layout.row().prop(pb, "MHScrunch")
+ layout.row().prop(pb, "MHLean")
+ return
+
+
+def registerPanels():
+ bpy.types.Object.FloatProperty(attr="PArmIK_L", name="L arm - IK", default = 0, min = 0.0, max = 1.0)
+ bpy.types.Object.FloatProperty(attr="PArmIK_R", name="R arm - IK", default = 0, min = 0.0, max = 1.0)
+ bpy.types.Object.FloatProperty(attr="PLegIK_L", name="L leg - IK", default = 0, min = 0.0, max = 1.0)
+ bpy.types.Object.FloatProperty(attr="PLegIK_R", name="R leg - IK", default = 0, min = 0.0, max = 1.0)
+
+ bpy.types.Object.FloatProperty(attr="PHandLocal_L", name="L hand - Loc", default = 0, min = 0.0, max = 1.0)
+ bpy.types.Object.FloatProperty(attr="PHandLocal_R", name="R hand - Loc", default = 0, min = 0.0, max = 1.0)
+ bpy.types.Object.FloatProperty(attr="PFootLocal_L", name="L foot - Loc", default = 0, min = 0.0, max = 1.0)
+ bpy.types.Object.FloatProperty(attr="PFootLocal_R", name="R foot - Loc", default = 0, min = 0.0, max = 1.0)
+
+ bpy.types.PoseBone.FloatProperty(attr="MHCone", name="Cone", default = 0, min = -0.5, max = 1.0)
+ bpy.types.PoseBone.FloatProperty(attr="MHRelax", name="Relax", default = 0, min = -0.5, max = 1.0)
+ bpy.types.PoseBone.FloatProperty(attr="MHCurl", name="Curl", default = 0, min = -0.5, max = 1.0)
+ bpy.types.PoseBone.FloatProperty(attr="MHLean", name="Lean", default = 0, min = -1.0, max = 1.0)
+ bpy.types.PoseBone.FloatProperty(attr="MHScrunch", name="Scrunch", default = 0, min = -0.5, max = 1.0)
+ bpy.types.PoseBone.FloatProperty(attr="MHSpread", name="Spread", default = 0, min = -0.5, max = 1.0)
+
+ bpy.types.register(MakeHumanFKIKPanel)
+ bpy.types.register(MakeHumanFingerPanel)
+
+def unregisterPanels():
+ bpy.types.unregister(MakeHumanFKIKPanel)
+ bpy.types.unregister(MakeHumanFingerPanel)
+ '''
+
+def register():
+ # registerPanels()
+ bpy.types.register(IMPORT_OT_makehuman_mhx)
+ menu_func = lambda self, context: self.layout.operator(IMPORT_OT_makehuman_mhx.bl_idname, text="MakeHuman (.mhx)...")
+ bpy.types.INFO_MT_file_import.append(menu_func)
+ return
+
+def unregister():
+ # unregisterPanels()
+ bpy.types.unregister(IMPORT_OT_makehuman_mhx)
+ menu_func = lambda self, context: self.layout.operator(IMPORT_OT_makehuman_mhx.bl_idname, text="MakeHuman (.mhx)...")
+ bpy.types.INFO_MT_file_import.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
+
+#
+# Testing
+#
+"""
+theScale = 1.0
+
+toggle = T_Replace + T_Mesh + T_Armature + T_MHX + T_ArmIK + T_LegIK
+#rigLeg = T_Toes + T_GoboFoot
+#rigArm = T_ElbowPT + T_FingerCurl
+
+#readMhxFile("/home/thomas/makehuman/exports/foo-25.mhx")
+
+#toggle = T_Replace + T_Armature
+#readMhxFile("/home/thomas/makehuman/exports/foo-sintel-25.mhx")
+
+readMhxFile("C:/Documents and Settings/xxxxxxxxxxxxxxxxxxxx/Mina dokument/makehuman/exports/foo-25.mhx", 'Classic')
+#readMhxFile("/home/thomas/mhx5/test1.mhx")
+#readMhxFile("/home/thomas/myblends/gobo/gobo.mhx")
+#readMhxFile("/home/thomas/myblends/sintel/simple.mhx")
+"""
+
+
+
+
+
diff --git a/io_import_scene_unreal_psk.py b/io_import_scene_unreal_psk.py
new file mode 100644
index 00000000..50cb4bdd
--- /dev/null
+++ b/io_import_scene_unreal_psk.py
@@ -0,0 +1,595 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Version': '2.0' ported by Darknet
+
+Unreal Tournament PSK file to Blender mesh converter V1.0
+Author: D.M. Sturgeon (camg188 at the elYsium forum), ported by Darknet
+Imports a *psk file to a new mesh
+
+-No UV Texutre
+-No Weight
+-No Armature Bones
+-No Material ID
+-Export Text Log From Current Location File (Bool )
+"""
+
+bl_addon_info = {
+ 'name': 'Import: Unreal Skeleton Mesh(.psk)',
+ 'author': 'Darknet',
+ 'version': '2.0',
+ 'blender': (2, 5, 3),
+ 'location': 'File > Import ',
+ 'description': 'Import Unreal Engine (.psk)',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/'\
+ 'Scripts/File_I-O/Unreal_psk_psa',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21366&group_id=153&atid=469',
+ 'category': 'Import/Export'}
+
+
+import bpy
+import mathutils
+import os
+import sys
+import string
+import math
+import re
+from string import *
+from struct import *
+from math import *
+
+#from bpy.props import *
+
+import mathutils
+
+#output log in to txt file
+DEBUGLOG = False
+
+scale = 1.0
+bonesize = 1.0
+md5_bones=[]
+
+def unpack_list(list_of_tuples):
+ l = []
+ for t in list_of_tuples:
+ l.extend(t)
+ return l
+"""
+class md5_bone:
+ bone_index=0
+ name=""
+ bindpos=[]
+ bindmat = mathutils.Quaternion()
+ parent=""
+ parent_index=0
+ blenderbone=None
+ roll=0
+
+ def __init__(self):
+ self.bone_index=0
+ self.name=""
+ self.bindpos=[0.0]*3
+ self.bindmat=[None]*3 #is this how you initilize a 2d-array
+ for i in range(3): self.bindmat[i] = [0.0]*3
+ self.parent=""
+ self.parent_index=0
+ self.blenderbone=None
+
+ def dump(self):
+ print ("bone index: ", self.bone_index)
+ print ("name: ", self.name)
+ print ("bind position: ", self.bindpos)
+ print ("bind translation matrix: ", self.bindmat)
+ print ("parent: ", self.parent)
+ print ("parent index: ", self.parent_index)
+ print ("blenderbone: ", self.blenderbone)
+"""
+class md5_bone:
+ bone_index=0
+ name=""
+ bindpos=[]
+ bindmat=[]
+ scale = []
+ parent=""
+ parent_index=0
+ blenderbone=None
+ roll=0
+
+ def __init__(self):
+ self.bone_index=0
+ self.name=""
+ self.bindpos=[0.0]*3
+ self.scale=[0.0]*3
+ self.bindmat=[None]*3 #is this how you initilize a 2d-array
+ for i in range(3): self.bindmat[i] = [0.0]*3
+ self.parent=""
+ self.parent_index=0
+ self.blenderbone=None
+
+ def dump(self):
+ print ("bone index: ", self.bone_index)
+ print ("name: ", self.name)
+ print ("bind position: ", self.bindpos)
+ print ("bind translation matrix: ", self.bindmat)
+ print ("parent: ", self.parent)
+ print ("parent index: ", self.parent_index)
+ print ("blenderbone: ", self.blenderbone)
+
+#http://www.blender.org/forum/viewtopic.php?t=13340&sid=8b17d5de07b17960021bbd72cac0495f
+def fixRollZ(b):
+ v = (b.tail-b.head)/b.length
+ b.roll -= math.degrees(math.atan2(v[0]*v[2]*(1 - v[1]),v[0]*v[0] + v[1]*v[2]*v[2]))
+def fixRoll(b):
+ v = (b.tail-b.head)/b.length
+ if v[2]*v[2] > .5:
+ #align X-axis
+ b.roll += math.degrees(math.atan2(v[0]*v[2]*(1 - v[1]),v[2]*v[2] + v[1]*v[0]*v[0]))
+ else:
+ #align Z-axis
+ b.roll -= math.degrees(math.atan2(v[0]*v[2]*(1 - v[1]),v[0]*v[0] + v[1]*v[2]*v[2]))
+
+def pskimport(infile):
+ global DEBUGLOG
+ print ("--------------------------------------------------")
+ print ("---------SCRIPT EXECUTING PYTHON IMPORTER---------")
+ print ("--------------------------------------------------")
+ print ("Importing file: ", infile)
+
+ md5_bones=[]
+ pskfile = open(infile,'rb')
+ if (DEBUGLOG):
+ logpath = infile.replace(".psk", ".txt")
+ print("logpath:",logpath)
+ logf = open(logpath,'w')
+
+ def printlog(strdata):
+ if (DEBUGLOG):
+ logf.write(strdata)
+
+ objName = infile.split('\\')[-1].split('.')[0]
+
+ me_ob = bpy.data.meshes.new(objName)
+ print("objName:",objName)
+ printlog(("New Mesh = " + me_ob.name + "\n"))
+ #read general header
+ indata = unpack('20s3i',pskfile.read(32))
+ #not using the general header at this time
+ #==================================================================================================
+ # vertex point
+ #==================================================================================================
+ #read the PNTS0000 header
+ indata = unpack('20s3i',pskfile.read(32))
+ recCount = indata[3]
+ printlog(( "Nbr of PNTS0000 records: " + str(recCount) + "\n"))
+ counter = 0
+ verts = []
+ while counter < recCount:
+ counter = counter + 1
+ indata = unpack('3f',pskfile.read(12))
+ #print(indata[0],indata[1],indata[2])
+ verts.extend([(indata[0],indata[1],indata[2])])
+ #Tmsh.verts.append(NMesh.Vert(indata[0],indata[1],indata[2]))
+
+ #==================================================================================================
+ # UV
+ #==================================================================================================
+ #read the VTXW0000 header
+ indata = unpack('20s3i',pskfile.read(32))
+ recCount = indata[3]
+ printlog( "Nbr of VTXW0000 records: " + str(recCount)+ "\n")
+ counter = 0
+ UVCoords = []
+ #UVCoords record format = [index to PNTS, U coord, v coord]
+ while counter < recCount:
+ counter = counter + 1
+ indata = unpack('hhffhh',pskfile.read(16))
+ UVCoords.append([indata[0],indata[2],indata[3]])
+ #print([indata[0],indata[2],indata[3]])
+ #print([indata[1],indata[2],indata[3]])
+
+ #==================================================================================================
+ # Face
+ #==================================================================================================
+ #read the FACE0000 header
+ indata = unpack('20s3i',pskfile.read(32))
+ recCount = indata[3]
+ printlog( "Nbr of FACE0000 records: "+ str(recCount) + "\n")
+ #PSK FACE0000 fields: WdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp
+ #associate MatIdx to an image, associate SmthGrp to a material
+ SGlist = []
+ counter = 0
+ faces = []
+ faceuv = []
+ while counter < recCount:
+ counter = counter + 1
+ indata = unpack('hhhbbi',pskfile.read(12))
+ #the psk values are: nWdgIdx1|WdgIdx2|WdgIdx3|MatIdx|AuxMatIdx|SmthGrp
+ #indata[0] = index of UVCoords
+ #UVCoords[indata[0]]=[index to PNTS, U coord, v coord]
+ #UVCoords[indata[0]][0] = index to PNTS
+ PNTSA = UVCoords[indata[0]][0]
+ PNTSB = UVCoords[indata[1]][0]
+ PNTSC = UVCoords[indata[2]][0]
+ #print(PNTSA,PNTSB,PNTSC) #face id vertex
+ #faces.extend([0,1,2,0])
+ faces.extend([PNTSA,PNTSB,PNTSC,0])
+ uv = []
+ u0 = UVCoords[indata[0]][1]
+ v0 = UVCoords[indata[0]][2]
+ uv.append([u0,v0])
+ u1 = UVCoords[indata[1]][1]
+ v1 = UVCoords[indata[1]][2]
+ uv.append([u1,v1])
+ u2 = UVCoords[indata[2]][1]
+ v2 = UVCoords[indata[2]][2]
+ uv.append([u2,v2])
+ faceuv.append(uv)
+ #print("UV: ",u0,v0)
+ #update the uv var of the last item in the Tmsh.faces list
+ # which is the face just added above
+ ##Tmsh.faces[-1].uv = [(u0,v0),(u1,v1),(u2,v2)]
+ #print("smooth:",indata[5])
+ #collect a list of the smoothing groups
+ if SGlist.count(indata[5]) == 0:
+ SGlist.append(indata[5])
+ print("smooth:",indata[5])
+ #assign a material index to the face
+ #Tmsh.faces[-1].materialIndex = SGlist.index(indata[5])
+ printlog( "Using Materials to represent PSK Smoothing Groups...\n")
+ #==========
+ # skip something...
+ #==========
+
+ #==================================================================================================
+ # Material
+ #==================================================================================================
+ ##
+ #read the MATT0000 header
+ indata = unpack('20s3i',pskfile.read(32))
+ recCount = indata[3]
+ printlog("Nbr of MATT0000 records: " + str(recCount) + "\n" )
+ printlog(" - Not importing any material data now. PSKs are texture wrapped! \n")
+ counter = 0
+ while counter < recCount:
+ counter = counter + 1
+ indata = unpack('64s6i',pskfile.read(88))
+ ##
+
+ #==================================================================================================
+ # Bones (Armature)
+ #==================================================================================================
+ #read the REFSKEL0 header
+ indata = unpack('20s3i',pskfile.read(32))
+ recCount = indata[3]
+ printlog( "Nbr of REFSKEL0 records: " + str(recCount) + "\n")
+ Bns = []
+ bone = []
+ nobone = 0
+ #==================================================================================================
+ # Bone Data
+ #==================================================================================================
+ counter = 0
+ print ("---PRASE--BONES---")
+ while counter < recCount:
+ indata = unpack('64s3i11f',pskfile.read(120))
+ #print( "DATA",str(indata))
+ bone.append(indata)
+
+ createbone = md5_bone()
+ #temp_name = indata[0][:30]
+ temp_name = indata[0]
+
+ temp_name = bytes.decode(temp_name)
+ temp_name = temp_name.lstrip(" ")
+ temp_name = temp_name.rstrip(" ")
+ temp_name = temp_name.strip()
+ temp_name = temp_name.strip( bytes.decode(b'\x00'))
+ print ("temp_name:", temp_name, "||")
+ createbone.name = temp_name
+ createbone.bone_index = counter
+ createbone.parent_index = indata[3]
+ createbone.bindpos[0] = indata[8]
+ createbone.bindpos[1] = indata[9]
+ createbone.bindpos[2] = indata[10]
+ createbone.scale[0] = indata[12]
+ createbone.scale[1] = indata[13]
+ createbone.scale[2] = indata[14]
+
+ #w,x,y,z
+ if (counter == 0):#main parent
+ print("no parent bone")
+ createbone.bindmat = mathutils.Quaternion((indata[7],indata[4],indata[5],indata[6]))
+ #createbone.bindmat = mathutils.Quaternion((indata[7],-indata[4],-indata[5],-indata[6]))
+ else:#parent
+ print("parent bone")
+ createbone.bindmat = mathutils.Quaternion((indata[7],-indata[4],-indata[5],-indata[6]))
+ #createbone.bindmat = mathutils.Quaternion((indata[7],indata[4],indata[5],indata[6]))
+
+ md5_bones.append(createbone)
+ counter = counter + 1
+ bnstr = (str(indata[0]))
+ Bns.append(bnstr)
+
+ for pbone in md5_bones:
+ pbone.parent = md5_bones[pbone.parent_index].name
+
+ bonecount = 0
+ for armbone in bone:
+ temp_name = armbone[0][:30]
+ #print ("BONE NAME: ",len(temp_name))
+ temp_name=str((temp_name))
+ #temp_name = temp_name[1]
+ #print ("BONE NAME: ",temp_name)
+ bonecount +=1
+ print ("-------------------------")
+ print ("----Creating--Armature---")
+ print ("-------------------------")
+
+ #================================================================================================
+ #Check armature if exist if so create or update or remove all and addnew bone
+ #================================================================================================
+ #bpy.ops.object.mode_set(mode='OBJECT')
+ meshname ="ArmObject"
+ objectname = "armaturedata"
+ bfound = False
+ arm = None
+ for obj in bpy.data.objects:
+ if (obj.name == meshname):
+ bfound = True
+ arm = obj
+ break
+
+ if bfound == False:
+ armdata = bpy.data.armatures.new(objectname)
+ ob_new = bpy.data.objects.new(meshname, armdata)
+ #ob_new = bpy.data.objects.new(meshname, 'ARMATURE')
+ #ob_new.data = armdata
+ bpy.context.scene.objects.link(ob_new)
+ #bpy.ops.object.mode_set(mode='OBJECT')
+ for i in bpy.context.scene.objects: i.select = False #deselect all objects
+ ob_new.select = True
+ #set current armature to edit the bone
+ bpy.context.scene.objects.active = ob_new
+ #set mode to able to edit the bone
+ bpy.ops.object.mode_set(mode='EDIT')
+ #newbone = ob_new.data.edit_bones.new('test')
+ #newbone.tail.y = 1
+ print("creating bone(s)")
+ for bone in md5_bones:
+ #print(dir(bone))
+ newbone = ob_new.data.edit_bones.new(bone.name)
+ #parent the bone
+ parentbone = None
+ print("bone name:",bone.name)
+ #note bone location is set in the real space or global not local
+ if bone.name != bone.parent:
+
+ pos_x = bone.bindpos[0]
+ pos_y = bone.bindpos[1]
+ pos_z = bone.bindpos[2]
+
+ #print( "LINKING:" , bone.parent ,"j")
+ parentbone = ob_new.data.edit_bones[bone.parent]
+ newbone.parent = parentbone
+ rotmatrix = bone.bindmat.to_matrix().resize4x4().rotation_part()
+
+ #parent_head = parentbone.head * parentbone.matrix.to_quat().inverse()
+ #parent_tail = parentbone.tail * parentbone.matrix.to_quat().inverse()
+ #location=Vector(pos_x,pos_y,pos_z)
+ #set_position = (parent_tail - parent_head) + location
+ #print("tmp head:",set_position)
+
+ #pos_x = set_position.x
+ #pos_y = set_position.y
+ #pos_z = set_position.z
+
+ newbone.head.x = parentbone.head.x + pos_x
+ newbone.head.y = parentbone.head.y + pos_y
+ newbone.head.z = parentbone.head.z + pos_z
+ print("head:",newbone.head)
+ newbone.tail.x = parentbone.head.x + (pos_x + bonesize * rotmatrix[1][0])
+ newbone.tail.y = parentbone.head.y + (pos_y + bonesize * rotmatrix[1][1])
+ newbone.tail.z = parentbone.head.z + (pos_z + bonesize * rotmatrix[1][2])
+ else:
+ rotmatrix = bone.bindmat.to_matrix().resize4x4().rotation_part()
+ newbone.head.x = bone.bindpos[0]
+ newbone.head.y = bone.bindpos[1]
+ newbone.head.z = bone.bindpos[2]
+ newbone.tail.x = bone.bindpos[0] + bonesize * rotmatrix[1][0]
+ newbone.tail.y = bone.bindpos[1] + bonesize * rotmatrix[1][1]
+ newbone.tail.z = bone.bindpos[2] + bonesize * rotmatrix[1][2]
+ #print("no parent")
+
+ bpy.context.scene.update()
+
+ #==================================================================================================
+ #END BONE DATA BUILD
+ #==================================================================================================
+ VtxCol = []
+ for x in range(len(Bns)):
+ #change the overall darkness of each material in a range between 0.1 and 0.9
+ tmpVal = ((float(x)+1.0)/(len(Bns))*0.7)+0.1
+ tmpVal = int(tmpVal * 256)
+ tmpCol = [tmpVal,tmpVal,tmpVal,0]
+ #Change the color of each material slightly
+ if x % 3 == 0:
+ if tmpCol[0] < 128: tmpCol[0] += 60
+ else: tmpCol[0] -= 60
+ if x % 3 == 1:
+ if tmpCol[1] < 128: tmpCol[1] += 60
+ else: tmpCol[1] -= 60
+ if x % 3 == 2:
+ if tmpCol[2] < 128: tmpCol[2] += 60
+ else: tmpCol[2] -= 60
+ #Add the material to the mesh
+ VtxCol.append(tmpCol)
+
+ #==================================================================================================
+ # Bone Weight
+ #==================================================================================================
+ #read the RAWW0000 header
+ indata = unpack('20s3i',pskfile.read(32))
+ recCount = indata[3]
+ printlog( "Nbr of RAWW0000 records: " + str(recCount) +"\n")
+ #RAWW0000 fields: Weight|PntIdx|BoneIdx
+ RWghts = []
+ counter = 0
+ while counter < recCount:
+ counter = counter + 1
+ indata = unpack('fii',pskfile.read(12))
+ RWghts.append([indata[1],indata[2],indata[0]])
+ #RWghts fields = PntIdx|BoneIdx|Weight
+ RWghts.sort()
+ printlog( "len(RWghts)=" + str(len(RWghts)) + "\n")
+ #Tmsh.update()
+
+ #set the Vertex Colors of the faces
+ #face.v[n] = RWghts[0]
+ #RWghts[1] = index of VtxCol
+ """
+ for x in range(len(Tmsh.faces)):
+ for y in range(len(Tmsh.faces[x].v)):
+ #find v in RWghts[n][0]
+ findVal = Tmsh.faces[x].v[y].index
+ n = 0
+ while findVal != RWghts[n][0]:
+ n = n + 1
+ TmpCol = VtxCol[RWghts[n][1]]
+ #check if a vertex has more than one influence
+ if n != len(RWghts)-1:
+ if RWghts[n][0] == RWghts[n+1][0]:
+ #if there is more than one influence, use the one with the greater influence
+ #for simplicity only 2 influences are checked, 2nd and 3rd influences are usually very small
+ if RWghts[n][2] < RWghts[n+1][2]:
+ TmpCol = VtxCol[RWghts[n+1][1]]
+ Tmsh.faces[x].col.append(NMesh.Col(TmpCol[0],TmpCol[1],TmpCol[2],0))
+ """
+ if (DEBUGLOG):
+ logf.close()
+ #==================================================================================================
+ #Building Mesh
+ #==================================================================================================
+ print("vertex:",len(verts),"faces:",len(faces))
+ me_ob.add_geometry(len(verts), 0, int(len(faces)/4))
+ me_ob.verts.foreach_set("co", unpack_list(verts))
+
+ me_ob.faces.foreach_set("verts_raw", faces)
+ me_ob.faces.foreach_set("smooth", [False] * len(me_ob.faces))
+ me_ob.update()
+
+ #===================================================================================================
+ #UV Setup
+ #===================================================================================================
+ texture = []
+ texturename = "text1"
+ #print(dir(bpy.data))
+ if (len(faceuv) > 0):
+ me_ob.add_uv_texture() #add one uv texture
+ for i, face in enumerate(me_ob.faces):
+ blender_tface= me_ob.uv_textures[0].data[i] #face
+ blender_tface.uv1 = faceuv[i][0] #uv = (0,0)
+ blender_tface.uv2 = faceuv[i][1] #uv = (0,0)
+ blender_tface.uv3 = faceuv[i][2] #uv = (0,0)
+ texture.append(me_ob.uv_textures[0])
+
+ #for tex in me_ob.uv_textures:
+ #print("mesh tex:",dir(tex))
+ #print((tex.name))
+
+ #===================================================================================================
+ #Material Setup
+ #===================================================================================================
+ materialname = "mat"
+ materials = []
+
+ matdata = bpy.data.materials.new(materialname)
+ #color is 0 - 1 not in 0 - 255
+ #matdata.mirror_color=(float(0.04),float(0.08),float(0.44))
+ matdata.diffuse_color=(float(0.04),float(0.08),float(0.44))#blue color
+ #print(dir(me_ob.uv_textures[0].data))
+ texdata = None
+ texdata = bpy.data.textures[len(bpy.data.textures)-1]
+ if (texdata != None):
+ #print(texdata.name)
+ #print(dir(texdata))
+ texdata.name = "texturelist1"
+ matdata.active_texture = texdata
+ materials.append(matdata)
+ #matdata = bpy.data.materials.new(materialname)
+ #materials.append(matdata)
+ #= make sure the list isnt too big
+ for material in materials:
+ #add material to the mesh list of materials
+ me_ob.add_material(material)
+ #===================================================================================================
+ #
+ #===================================================================================================
+ obmesh = bpy.data.objects.new(objName,me_ob)
+ #check if there is a material to set to
+ if len(materials) > 0:
+ obmesh.active_material = materials[0] #material setup tmp
+
+ bpy.context.scene.objects.link(obmesh)
+
+ bpy.context.scene.update()
+
+ print ("PSK2Blender completed")
+#End of def pskimport#########################
+
+def getInputFilename(filename):
+ checktype = filename.split('\\')[-1].split('.')[1]
+ print ("------------",filename)
+ if checktype.upper() != 'PSK':
+ print (" Selected file = ",filename)
+ raise (IOError, "The selected input file is not a *.psk file")
+ pskimport(filename)
+
+from bpy.props import *
+
+class IMPORT_OT_psk(bpy.types.Operator):
+ '''Load a skeleton mesh psk File'''
+ bl_idname = "import_scene.psk"
+ bl_label = "Import PSK"
+
+ # List of operator properties, the attributes will be assigned
+ # to the class instance from the operator settings before calling.
+ filepath = StringProperty(name="File Path", description="Filepath used for importing the OBJ file", maxlen= 1024, default= "")
+
+ def execute(self, context):
+ getInputFilename(self.properties.filepath)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.manager
+ wm.add_fileselect(self)
+ return {'RUNNING_MODAL'}
+
+menu_func = lambda self, context: self.layout.operator(IMPORT_OT_psk.bl_idname, text="Skeleton Mesh (.psk)")
+
+def register():
+ bpy.types.register(IMPORT_OT_psk)
+ bpy.types.INFO_MT_file_import.append(menu_func)
+
+def unregister():
+ bpy.types.unregister(IMPORT_OT_psk)
+ bpy.types.INFO_MT_file_import.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
+#note this only read the data and will not be place in the scene
+#getInputFilename('C:\\blenderfiles\\BotA.psk')
+#getInputFilename('C:\\blenderfiles\\AA.PSK')
diff --git a/io_mesh_raw/__init__.py b/io_mesh_raw/__init__.py
new file mode 100644
index 00000000..1af09c9f
--- /dev/null
+++ b/io_mesh_raw/__init__.py
@@ -0,0 +1,63 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_addon_info = {
+ 'name': 'Import/Export: Raw mesh',
+ 'author': 'Anthony D,Agostino (Scorpius), Aurel Wildfellner',
+ 'version': '0.2',
+ 'blender': (2, 5, 3),
+ 'location': 'File > Import/Export > Raw faces ',
+ 'description': 'Import Raw Faces (.raw format)',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/File_I-O/Raw_Mesh_IO',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21733&group_id=153&atid=469',
+ 'category': 'Import/Export'}
+
+import bpy
+
+
+def menu_import(self, context):
+ from io_mesh_raw import import_raw
+ self.layout.operator(import_raw.RawImporter.bl_idname, text="Raw Faces (.raw)").filepath = "*.raw"
+
+
+def menu_export(self, context):
+ from io_mesh_raw import export_raw
+ import os
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".raw"
+ self.layout.operator(export_raw.RawExporter.bl_idname, text="Raw Faces (.raw)").filepath = default_path
+
+
+def register():
+ from io_mesh_raw import import_raw, export_raw
+ bpy.types.register(import_raw.RawImporter)
+ bpy.types.register(export_raw.RawExporter)
+ bpy.types.INFO_MT_file_import.append(menu_import)
+ bpy.types.INFO_MT_file_export.append(menu_export)
+
+def unregister():
+ from io_mesh_raw import import_raw, export_raw
+ bpy.types.unregister(import_raw.RawImporter)
+ bpy.types.unregister(export_raw.RawExporter)
+ bpy.types.INFO_MT_file_import.remove(menu_import)
+ bpy.types.INFO_MT_file_export.remove(menu_export)
+
+if __name__ == "__main__":
+ register()
diff --git a/io_mesh_raw/export_raw.py b/io_mesh_raw/export_raw.py
new file mode 100644
index 00000000..59f83810
--- /dev/null
+++ b/io_mesh_raw/export_raw.py
@@ -0,0 +1,112 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = ["Aurel Wildfellner"]
+__version__ = '0.2'
+__bpydoc__ = """\
+This script exports a Mesh to a RAW triangle format file.
+
+The raw triangle format is very simple; it has no verts or faces lists.
+It's just a simple ascii text file with the vertices of each triangle
+listed on each line. In addition, also quads can be exported as a line
+of 12 values (this was the default before blender 2.5). Now default
+settings will triangulate the mesh.
+
+Usage:<br>
+ Execute this script from the "File->Export" menu. You can select
+whether modifiers should be applied and if the mesh is triangulated.
+
+"""
+
+import bpy
+
+
+def faceToTriangles(face):
+ triangles = []
+ if (len(face) == 4): #quad
+ triangles.append( [ face[0], face[1], face[2] ] )
+ triangles.append( [ face[2], face[3], face[0] ] )
+ else:
+ triangles.append(face)
+
+ return triangles
+
+
+def faceValues(face, mesh, matrix):
+ fv = []
+ for verti in face.verts_raw:
+ fv.append(matrix * mesh.verts[verti].co)
+ return fv
+
+
+def faceToLine(face):
+ line = ""
+ for v in face:
+ line += str(v[0]) + " " + str(v[1]) + " " + str(v[2]) + " "
+ return line[:-1] + "\n"
+
+
+def export_raw(filepath, applyMods, triangulate):
+ faces = []
+ for obj in bpy.context.selected_objects:
+ if obj.type == 'MESH':
+ matrix = obj.matrix_world
+
+ if (applyMods):
+ me = obj.create_mesh(True, "PREVIEW")
+ else:
+ me = obj.data
+
+ for face in me.faces:
+ fv = faceValues(face, me, matrix)
+ if triangulate:
+ faces.extend(faceToTriangles(fv))
+ else:
+ faces.append(fv)
+
+ # write the faces to a file
+ file = open(filepath, "w")
+ for face in faces:
+ file.write(faceToLine(face))
+ file.close()
+
+
+from bpy.props import *
+
+
+class RawExporter(bpy.types.Operator):
+ '''Save Raw triangle mesh data'''
+ bl_idname = "export_mesh.raw"
+ bl_label = "Export RAW"
+
+ filepath = StringProperty(name="File Path", description="Filepath used for exporting the RAW file", maxlen= 1024, default= "")
+ check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'})
+
+ apply_modifiers = BoolProperty(name="Apply Modifiers", description="Use transformed mesh data from each object", default=True)
+ triangulate = BoolProperty(name="Triangulate", description="Triangulate quads.", default=True)
+
+ def execute(self, context):
+ export_raw(self.properties.filepath, self.properties.apply_modifiers, self.properties.triangulate)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.manager
+ wm.add_fileselect(self)
+ return {'RUNNING_MODAL'}
+
+# package manages registering
diff --git a/io_mesh_raw/import_raw.py b/io_mesh_raw/import_raw.py
new file mode 100644
index 00000000..df04df9b
--- /dev/null
+++ b/io_mesh_raw/import_raw.py
@@ -0,0 +1,139 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+__author__ = ["Anthony D'Agostino (Scorpius)", "Aurel Wildfellner"]
+__version__ = '0.2'
+__bpydoc__ = """\
+This script imports Raw Triangle File format files to Blender.
+
+The raw triangle format is very simple; it has no verts or faces lists.
+It's just a simple ascii text file with the vertices of each triangle
+listed on each line. In addition, a line with 12 values will be
+imported as a quad. This may be in conflict with some other
+applications, which use a raw format, but this is how it was
+implemented back in blender 2.42.
+
+Usage:<br>
+ Execute this script from the "File->Import" menu and choose a Raw file to
+open.
+
+Notes:<br>
+ Generates the standard verts and faces lists, but without duplicate
+verts. Only *exact* duplicates are removed, there is no way to specify a
+tolerance.
+"""
+
+
+
+import bpy
+
+# move those to a utility modul
+from import_scene_obj import unpack_face_list, unpack_list # TODO, make generic
+
+
+def readMesh(filename, objName):
+ file = open(filename, "rb")
+
+ def line_to_face(line):
+ # Each triplet is an xyz float
+ line_split = []
+ try:
+ line_split = list(map(float, line.split()))
+ except:
+ return None
+
+ if len(line_split) == 9: # Tri
+ f1, f2, f3, f4, f5, f6, f7, f8, f9 = line_split
+ return [(f1, f2, f3), (f4, f5, f6), (f7, f8, f9)]
+ elif len(line_split) == 12: # Quad
+ f1, f2, f3, f4, f5, f6, f7, f8, f9, A, B, C = line_split
+ return [(f1, f2, f3), (f4, f5, f6), (f7, f8, f9), (A, B, C)]
+ else:
+ return None
+
+
+ faces = []
+ for line in file.readlines():
+ face = line_to_face(line)
+ if face:
+ faces.append(face)
+
+ file.close()
+
+ # Generate verts and faces lists, without duplicates
+ verts = []
+ coords = {}
+ index = 0
+
+ for f in faces:
+ for i, v in enumerate(f):
+ try:
+ f[i] = coords[v]
+ except:
+ f[i] = coords[v] = index
+ index += 1
+ verts.append(v)
+
+ mesh = bpy.data.meshes.new(objName)
+ mesh.add_geometry(int(len(verts)), 0, int(len(faces)))
+ mesh.verts.foreach_set("co", unpack_list(verts))
+ mesh.faces.foreach_set("verts_raw", unpack_face_list(faces))
+
+ return mesh
+
+
+def addMeshObj(mesh, objName):
+ scn = bpy.context.scene
+
+ for o in scn.objects:
+ o.select = False
+
+ mesh.update()
+ nobj = bpy.data.objects.new(objName, mesh)
+ scn.objects.link(nobj)
+ nobj.select = True
+
+ if scn.objects.active == None or scn.objects.active.mode == 'OBJECT':
+ scn.objects.active = nobj
+
+
+from bpy.props import *
+
+class RawImporter(bpy.types.Operator):
+ '''Load Raw triangle mesh data'''
+ bl_idname = "import_mesh.raw"
+ bl_label = "Import RAW"
+
+ filepath = StringProperty(name="File Path", description="Filepath used for importing the RAW file", maxlen=1024, default="")
+
+ def execute(self, context):
+
+ #convert the filename to an object name
+ objName = bpy.utils.display_name(self.properties.filename)
+
+ mesh = readMesh(self.properties.filepath, objName)
+ addMeshObj(mesh, objName)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.manager
+ wm.add_fileselect(self)
+ return {'RUNNING_MODAL'}
+
+# package manages registering
diff --git a/io_mesh_stl/__init__.py b/io_mesh_stl/__init__.py
new file mode 100644
index 00000000..1858c17b
--- /dev/null
+++ b/io_mesh_stl/__init__.py
@@ -0,0 +1,152 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Import/Export STL files (binary or ascii)
+
+- Import automatically remove the doubles.
+- Export can export with/without modifiers applied
+
+Issues:
+
+Import:
+ - Does not handle the normal of the triangles
+ - Does not handle endien
+
+Export:
+ - Does not do the object space transformation
+ - Export only one object (the selected one)
+"""
+
+bl_addon_info = {
+ 'name': 'Import/Export: STL format',
+ 'author': 'Guillaume Bouchard (Guillaum)',
+ 'version': '1',
+ 'blender': (2, 5, 3),
+ 'location': 'File > Import/Export > Stl',
+ 'description': 'Import/Export Stl files',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/File I-O/STL', # @todo write the page
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?' \
+ 'func=detail&aid=22837&group_id=153&atid=469',
+ 'category': 'Import/Export'}
+
+import bpy
+from bpy.props import *
+
+from io_mesh_stl import stl_utils, blender_utils
+
+
+class StlImporter(bpy.types.Operator):
+ '''
+ Load STL triangle mesh data
+ '''
+ bl_idname = "import_mesh.stl"
+ bl_label = "Import STL"
+
+ filepath = StringProperty(name="File Path",
+ description="File path used for importing "
+ "the STL file",
+ maxlen=1024,
+ default="")
+
+ def execute(self, context):
+ objName = bpy.utils.display_name(self.properties.filepath.split("\\")[-1].split("/")[-1])
+ tris, pts = stl_utils.read_stl(self.properties.filepath)
+
+ blender_utils.create_and_link_mesh(objName, tris, pts)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.manager
+ wm.add_fileselect(self)
+
+ return {'RUNNING_MODAL'}
+
+
+class StlExporter(bpy.types.Operator):
+ '''
+ Save STL triangle mesh data from the active object
+ '''
+ bl_idname = "export_mesh.stl"
+ bl_label = "Export STL"
+
+ filepath = StringProperty(name="File Path",
+ description="File path used for exporting "
+ "the active object to STL file",
+ maxlen=1024,
+ default="")
+ check_existing = BoolProperty(name="Check Existing",
+ description="Check and warn on "
+ "overwriting existing files",
+ default=True,
+ options={'HIDDEN'})
+
+ ascii = BoolProperty(name="Ascii",
+ description="Save the file in ASCII file format",
+ default=False)
+ apply_modifiers = BoolProperty(name="Apply Modifiers",
+ description="Apply the modifiers "
+ "before saving",
+ default=True)
+
+ def execute(self, context):
+ ob = context.active_object
+
+ faces = blender_utils.faces_from_mesh(ob,
+ self.properties.apply_modifiers)
+ stl_utils.write_stl(self.properties.filepath, faces, self.properties.ascii)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.manager
+ wm.add_fileselect(self)
+ return {'RUNNING_MODAL'}
+
+
+def menu_import(self, context):
+ self.layout.operator(StlImporter.bl_idname,
+ text="Stl (.stl)").filepath = "*.stl"
+
+
+def menu_export(self, context):
+ import os
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".stl"
+ self.layout.operator(StlExporter.bl_idname,
+ text="Stl (.stl)").filepath = default_path
+
+
+def register():
+ bpy.types.register(StlImporter)
+ bpy.types.register(StlExporter)
+ bpy.types.INFO_MT_file_import.append(menu_import)
+ bpy.types.INFO_MT_file_export.append(menu_export)
+
+
+def unregister():
+ bpy.types.unregister(StlImporter)
+ bpy.types.unregister(StlExporter)
+ bpy.types.INFO_MT_file_import.remove(menu_import)
+ bpy.types.INFO_MT_file_export.remove(menu_export)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/io_mesh_stl/blender_utils.py b/io_mesh_stl/blender_utils.py
new file mode 100644
index 00000000..1bade61d
--- /dev/null
+++ b/io_mesh_stl/blender_utils.py
@@ -0,0 +1,50 @@
+import bpy
+
+
+def create_and_link_mesh(name, faces, points):
+ '''
+ Create a blender mesh and object called name from a list of
+ *points* and *faces* and link it in the current scene.
+ '''
+
+ mesh = bpy.data.meshes.new(name)
+ mesh.from_pydata(points, [], faces)
+
+ ob = bpy.data.objects.new(name, mesh)
+ bpy.context.scene.objects.link(ob)
+
+ # update mesh to allow proper display
+ mesh.update()
+
+
+def faces_from_mesh(ob, apply_modifier=False, triangulate=True):
+ '''
+ From an object, return a generator over a list of faces.
+
+ Each faces is a list of his vertexes. Each vertex is a tuple of
+ his coordinate.
+
+ apply_modifier
+ Apply the preview modifier to the returned liste
+
+ triangulate
+ Split the quad into two triangles
+ '''
+
+ # get the modifiers
+ mesh = ob.create_mesh(bpy.context.scene,
+ True, "PREVIEW") if apply_modifier else ob.data
+
+ def iter_face_index():
+ '''
+ From a list of faces, return the face triangulated if needed.
+ '''
+ for face in mesh.faces:
+ if triangulate and len(face.verts) == 4:
+ yield face.verts[:3]
+ yield face.verts[2:] + [face.verts[0]]
+ else:
+ yield list(face.verts)
+
+ return ([tuple(mesh.verts[index].co)
+ for index in indexes] for indexes in iter_face_index())
diff --git a/io_mesh_stl/stl_utils.py b/io_mesh_stl/stl_utils.py
new file mode 100644
index 00000000..34db045c
--- /dev/null
+++ b/io_mesh_stl/stl_utils.py
@@ -0,0 +1,228 @@
+'''
+Import and export STL files
+
+Used as a blender script, it load all the stl files in the scene:
+
+blender -P stl_utils.py -- file1.stl file2.stl file3.stl ...
+'''
+
+import struct
+import mmap
+import contextlib
+import itertools
+
+# TODO: endien
+
+
+@contextlib.contextmanager
+def mmap_file(filename):
+ '''
+ Context manager over the data of an mmap'ed file (Read ONLY).
+
+
+ Example:
+
+ with mmap_file(filename) as m:
+ m.read()
+ print m[10:50]
+ '''
+ with open(filename, 'rb') as file:
+ # check http://bugs.python.org/issue8046 to have mmap context
+ # manager fixed in python
+ map = mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ)
+ yield map
+ map.close()
+
+
+class ListDict(dict):
+ '''
+ Set struct with order.
+
+ You can:
+ - insert data into without doubles
+ - get the list of data in insertion order with self.list
+
+ Like collections.OrderedDict, but quicker, can be replaced if
+ ODict is optimised.
+ '''
+
+ def __init__(self):
+ dict.__init__(self)
+ self.list = []
+ self._len = 0
+
+ def add(self, item):
+ '''
+ Add a value to the Set, return its position in it.
+ '''
+ value = self.setdefault(item, self._len)
+ if value == self._len:
+ self.list.append(item)
+ self._len += 1
+
+ return value
+
+
+def _binary_read(data):
+ # an stl binary file is
+ # - 80 bytes of description
+ # - 2 bytes of size (unsigned int)
+ # - size triangles :
+ #
+ # - 12 bytes of normal
+ # - 9 * 4 bytes of coordinate (3*3 floats)
+ # - 2 bytes of garbage (usually 0)
+
+ # OFFSET for the first byte of coordinate (headers + first normal bytes)
+ # STRIDE between each triangle (first normal + coordinates + garbage)
+ OFFSET, STRIDE = 84 + 12, 12 * 4 + 2
+
+ # read header size, ignore description
+ size = struct.unpack_from('<I', data, 80)[0]
+ unpack = struct.Struct('<9f').unpack_from
+
+ for i in range(size):
+ # read the points coordinates of each triangle
+ pt = unpack(data, OFFSET + STRIDE * i)
+ yield pt[:3], pt[3:6], pt[6:]
+
+
+def _ascii_read(data):
+ # an stl ascii file is like
+ # HEADER: solid some name
+ # for each face:
+ #
+ # facet normal x y z
+ # outerloop
+ # vertex x y z
+ # vertex x y z
+ # vertex x y z
+ # endloop
+ # endfacet
+
+ # strip header
+ data.readline()
+
+ while True:
+ # strip facet normal // or end
+ data.readline()
+
+ # strip outer loup, in case of ending, break the loop
+ if not data.readline():
+ break
+
+ yield [tuple(map(float, data.readline().split()[1:]))
+ for _ in range(3)]
+
+ # strip facet normalend and outerloop end
+ data.readline()
+ data.readline()
+
+
+def _binary_write(filename, faces):
+ with open(filename, 'wb') as data:
+ # header
+ # we write padding at header begginning to avoid to
+ # call len(list(faces)) which may be expensive
+ data.write(struct.calcsize('<80sI') * b'\0')
+
+ # 3 vertex == 9f
+ pack = struct.Struct('<9f').pack
+ # pad is to remove normal, we do use them
+ pad = b'\0' * struct.calcsize('<3f')
+
+ nb = 0
+ for verts in faces:
+ # write pad as normal + vertexes + pad as attributes
+ data.write(pad + pack(*itertools.chain.from_iterable(verts)))
+ data.write(b'\0\0')
+ nb += 1
+
+ # header, with correct value now
+ data.seek(0)
+ data.write(struct.pack('<80sI', b"Exported from blender", nb))
+
+
+def _ascii_write(filename, faces):
+ with open(filename, 'w') as data:
+ data.write('solid Exported from blender\n')
+
+ for face in faces:
+ data.write('''facet normal 0 0 0\nouter loop\n''')
+ for vert in face:
+ data.write('vertex %f %f %f\n' % vert)
+ data.write('endloop\nendfacet\n')
+
+ data.write('endsolid Exported from blender\n')
+
+
+def write_stl(filename, faces, ascii=False):
+ '''
+ Write a stl file from faces,
+
+ filename
+ output filename
+
+ faces
+ iterable of tuple of 3 vertex, vertex is tuple of 3 coordinates as float
+
+ ascii
+ save the file in ascii format (very huge)
+ '''
+ (_ascii_write if ascii else _binary_write)(filename, faces)
+
+
+def read_stl(filename):
+ '''
+ Return the triangles and points of an stl binary file.
+
+ Please note that this process can take lot of time if the file is
+ huge (~1m30 for a 1 Go stl file on an quad core i7).
+
+ - returns a tuple(triangles, points).
+
+ triangles
+ A list of triangles, each triangle as a tuple of 3 index of
+ point in *points*.
+
+ points
+ An indexed list of points, each point is a tuple of 3 float
+ (xyz).
+
+ Example of use:
+
+ >>> tris, pts = read_stl(filename, lambda x:)
+ >>> pts = list(pts)
+ >>>
+ >>> # print the coordinate of the triangle n
+ >>> print([pts[i] for i in tris[n]])
+ '''
+
+ tris, pts = [], ListDict()
+
+ with mmap_file(filename) as data:
+ # check for ascii or binary
+ gen = _ascii_read if data.read(5) == b'solid' else _binary_read
+
+ for pt in gen(data):
+ # Add the triangle and the point.
+ # If the point is allready in the list of points, the
+ # index returned by pts.add() will be the one from the
+ # first equal point inserted.
+ tris.append([pts.add(p) for p in pt])
+
+ return tris, pts.list
+
+
+if __name__ == '__main__':
+ import sys
+ import bpy
+ from io_mesh_stl import blender_utils
+
+ filenames = sys.argv[sys.argv.index('--') + 1:]
+
+ for filename in filenames:
+ objName = bpy.utils.display_name(filename)
+ tris, pts = read_stl(filename)
+
+ blender_utils.create_and_link_mesh(objName, tris, pts)
diff --git a/mesh_relax.py b/mesh_relax.py
new file mode 100644
index 00000000..7d3410d7
--- /dev/null
+++ b/mesh_relax.py
@@ -0,0 +1,128 @@
+# mesh_relax.py Copyright (C) 2010, Fabian Fricke
+#
+# Relaxes selected vertices while retaining the shape as much as possible
+#
+# ***** 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 *****
+
+"""
+
+Usage:
+
+Launch from "W-menu" or from "Mesh -> Vertices -> Relax"
+
+
+Additional links:
+ Author Site: http://frigi.designdevil.de
+ e-mail: frigi.f {at} gmail {dot} com
+
+"""
+
+
+bl_addon_info = {
+ 'name': 'Mesh: Relax',
+ 'author': 'Fabian Fricke',
+ 'version': '1.1 2010/04/22',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Specials > Relax ',
+ 'description': 'Relax the selected verts while retaining the shape',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Modeling/Relax',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21421&group_id=153&atid=469',
+ 'category': 'Mesh'}
+
+
+import bpy
+from bpy.props import IntProperty
+
+def relax_mesh(context):
+
+ # deselect everything that's not related
+ for obj in context.selected_objects:
+ obj.select = False
+
+ # get active object
+ obj = context.active_object
+
+ # duplicate the object so it can be used for the shrinkwrap modifier
+ obj.select = True # make sure the object is selected!
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.duplicate()
+ target = context.active_object
+
+ # remove all other modifiers from the target
+ for m in range(0, len(target.modifiers)):
+ target.modifiers.remove(target.modifiers[0])
+
+ context.scene.objects.active = obj
+
+ sw = obj.modifiers.new(type='SHRINKWRAP', name='relax_target')
+ sw.target = target
+
+ # run smooth operator to relax the mesh
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.vertices_smooth()
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # apply the modifier
+ bpy.ops.object.modifier_apply(modifier='relax_target')
+
+ # delete the target object
+ obj.select = False
+ target.select = True
+ bpy.ops.object.delete()
+
+ # go back to initial state
+ obj.select = True
+ bpy.ops.object.mode_set(mode='EDIT')
+
+class Relax(bpy.types.Operator):
+ '''Relaxes selected vertices while retaining the shape as much as possible'''
+ bl_idname = 'mesh.relax'
+ bl_label = 'Relax'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ iterations = IntProperty(name="Relax iterations",
+ default=1, min=0, max=100, soft_min=0, soft_max=10)
+
+ def poll(self, context):
+ obj = context.active_object
+ return (obj and obj.type == 'MESH')
+
+ def execute(self, context):
+ for i in range(0,self.properties.iterations):
+ relax_mesh(context)
+ return {'FINISHED'}
+
+menu_func = (lambda self, context: self.layout.operator(Relax.bl_idname, text="Relax"))
+
+def register():
+ bpy.types.register(Relax)
+ bpy.types.VIEW3D_MT_edit_mesh_specials.append(menu_func)
+ bpy.types.VIEW3D_MT_edit_mesh_vertices.append(menu_func)
+
+def unregister():
+ bpy.types.unregister(Relax)
+ bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_func)
+ bpy.types.VIEW3D_MT_edit_mesh_vertices.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/mesh_surface_sketch.py b/mesh_surface_sketch.py
new file mode 100644
index 00000000..37bc2bc7
--- /dev/null
+++ b/mesh_surface_sketch.py
@@ -0,0 +1,827 @@
+# ##### 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 #####
+
+
+bl_addon_info = {
+ 'name': 'Mesh: Surface Sketch',
+ 'author': 'Eclectiel',
+ 'version': '0.8 Beta',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > EditMode > ToolShelf',
+ 'description': 'Draw meshes and re-topologies with Grease Pencil',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Mesh/Surface_Sketch',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22062&group_id=153&atid=469',
+ 'category': 'Mesh'}
+
+
+import bpy
+import math
+
+from math import *
+
+
+bpy.types.Scene.IntProperty(attr = "SURFSK_edges_U", name = "Cross", description = "Number of edge rings crossing the strokes (perpendicular to strokes direction)", default = 10, min = 0, max = 100000)
+bpy.types.Scene.IntProperty(attr = "SURFSK_edges_V", name = "Follow", description = "Number of edge rings following the strokes (parallel to strokes direction)", default = 10, min = 0, max = 100000)
+bpy.types.Scene.IntProperty(attr = "SURFSK_precision", name = "Precision", description = "Precision level of the surface calculation", default = 4, min = 0, max = 100000)
+bpy.types.Scene.BoolProperty(attr = "SURFSK_keep_strokes", name = "Keep strokes", description = "Keeps the sketched strokes after adding the surface", default = False)
+
+
+
+
+class View3DPanel(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+
+
+class VIEW3D_PT_tools_SURF_SKETCH(View3DPanel):
+ bl_context = "mesh_edit"
+ bl_label = "Surface Sketching"
+
+ def poll(self, context):
+ return context.active_object
+
+ def draw(self, context):
+ layout = self.layout
+
+ scn = context.scene
+ ob = context.object
+
+ col = layout.column(align=True)
+ row = layout.row()
+ row.separator()
+ col.operator("GPENCIL_OT_SURFSK_add_surface", text="Add Surface")
+ col.prop(scn, "SURFSK_edges_U")
+ col.prop(scn, "SURFSK_edges_V")
+ row.separator()
+ col.prop(scn, "SURFSK_keep_strokes")
+ col.separator()
+ row.separator()
+ col.operator("GPENCIL_OT_SURFSK_strokes_to_curves", text="Strokes to curves")
+
+
+
+class GPENCIL_OT_SURFSK_add_surface(bpy.types.Operator):
+ bl_idname = "GPENCIL_OT_SURFSK_add_surface"
+ bl_label = "Surface generation from grease pencil strokes"
+ bl_description = "Surface generation from grease pencil strokes"
+
+
+ #### Get an ordered list of a chain of vertices.
+ def get_ordered_verts(self, ob, all_selected_edges_idx, all_selected_verts_idx, first_vert_idx, middle_vertex_idx):
+ # Order selected vertexes.
+ verts_ordered = []
+ verts_ordered.append(self.main_object.data.verts[first_vert_idx])
+ prev_v = first_vert_idx
+ prev_ed = None
+ finish_while = False
+ while True:
+ edges_non_matched = 0
+ for i in all_selected_edges_idx:
+ if ob.data.edges[i] != prev_ed and ob.data.edges[i].verts[0] == prev_v and ob.data.edges[i].verts[1] in all_selected_verts_idx:
+ verts_ordered.append(self.main_object.data.verts[ob.data.edges[i].verts[1]])
+ prev_v = ob.data.edges[i].verts[1]
+ prev_ed = ob.data.edges[i]
+ elif ob.data.edges[i] != prev_ed and ob.data.edges[i].verts[1] == prev_v and ob.data.edges[i].verts[0] in all_selected_verts_idx:
+ verts_ordered.append(self.main_object.data.verts[ob.data.edges[i].verts[0]])
+ prev_v = ob.data.edges[i].verts[0]
+ prev_ed = ob.data.edges[i]
+ else:
+ edges_non_matched += 1
+
+ if edges_non_matched == len(all_selected_edges_idx):
+ finish_while = True
+
+ if finish_while:
+ break
+
+ if middle_vertex_idx != None:
+ verts_ordered.append(self.main_object.data.verts[middle_vertex_idx])
+ verts_ordered.reverse()
+
+ return verts_ordered
+
+
+ #### Calculates length of a chain of points.
+ def get_chain_length(self, verts_ordered):
+ edges_lengths = []
+ edges_lengths_sum = 0
+ for i in range(0, len(verts_ordered)):
+ if i == 0:
+ prev_v = verts_ordered[i]
+ else:
+ v = verts_ordered[i]
+
+ v_difs = [prev_v.co[0] - v.co[0], prev_v.co[1] - v.co[1], prev_v.co[2] - v.co[2]]
+ edge_length = abs(sqrt(v_difs[0] * v_difs[0] + v_difs[1] * v_difs[1] + v_difs[2] * v_difs[2]))
+
+ edges_lengths.append(edge_length)
+ edges_lengths_sum += edge_length
+
+ prev_v = v
+
+ return edges_lengths, edges_lengths_sum
+
+
+ #### Calculates the proportion of the edges of a chain of edges, relative to the full chain length.
+ def get_edges_proportions(self, edges_lengths, edges_lengths_sum, use_boundaries, fixed_edges_num):
+ edges_proportions = []
+ if use_boundaries:
+ verts_count = 1
+ for l in edges_lengths:
+ edges_proportions.append(l / edges_lengths_sum)
+ verts_count += 1
+ else:
+ verts_count = 1
+ for n in range(0, fixed_edges_num):
+ edges_proportions.append(1 / fixed_edges_num)
+ verts_count += 1
+
+ return edges_proportions
+
+
+ #### Calculates the angle between two pairs of points in space.
+ def orientation_difference(self, points_A_co, points_B_co): # each parameter should be a list with two elements, and each element should be a x,y,z coordinate.
+ vec_A = points_A_co[0] - points_A_co[1]
+ vec_B = points_B_co[0] - points_B_co[1]
+
+ angle = vec_A.angle(vec_B)
+
+ if angle > 0.5 * math.pi:
+ angle = abs(angle - math.pi)
+
+ return angle
+
+
+ #### Calculate distance between two points
+ def pts_distance(self, p1_co, p2_co):
+ p_difs = [p1_co[0] - p2_co[0], p1_co[1] - p2_co[1], p1_co[2] - p2_co[2]]
+ distance = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
+
+ return distance
+
+
+ def execute(self, context):
+ #### Selected edges.
+ all_selected_edges_idx = []
+ all_selected_verts = []
+ all_verts_idx = []
+ for ed in self.main_object.data.edges:
+ if ed.select:
+ all_selected_edges_idx.append(ed.index)
+
+ # Selected vertexes.
+ if not ed.verts[0] in all_selected_verts:
+ all_selected_verts.append(self.main_object.data.verts[ed.verts[0]])
+ if not ed.verts[1] in all_selected_verts:
+ all_selected_verts.append(self.main_object.data.verts[ed.verts[1]])
+
+ # All verts (both from each edge) to determine later which are at the tips (those not repeated twice).
+ all_verts_idx.append(ed.verts[0])
+ all_verts_idx.append(ed.verts[1])
+
+
+ #### Identify the tips and "middle-vertex" that separates U from V, if there is one.
+ all_chains_tips_idx = []
+ for v_idx in all_verts_idx:
+ if all_verts_idx.count(v_idx) < 2:
+ all_chains_tips_idx.append(v_idx)
+
+ edges_connected_to_tips = []
+ for ed in self.main_object.data.edges:
+ if (ed.verts[0] in all_chains_tips_idx or ed.verts[1] in all_chains_tips_idx) and not (ed.verts[0] in all_verts_idx and ed.verts[1] in all_verts_idx):
+ edges_connected_to_tips.append(ed)
+
+ middle_vertex_idx = None
+ tips_to_discard_idx = []
+ for ed_tips in edges_connected_to_tips:
+ for ed_tips_b in edges_connected_to_tips:
+ if (ed_tips != ed_tips_b):
+ if ed_tips.verts[0] in all_verts_idx and (((ed_tips.verts[1] == ed_tips_b.verts[0]) or ed_tips.verts[1] == ed_tips_b.verts[1])):
+ middle_vertex_idx = ed_tips.verts[1]
+ tips_to_discard_idx.append(ed_tips.verts[0])
+ elif ed_tips.verts[1] in all_verts_idx and (((ed_tips.verts[0] == ed_tips_b.verts[0]) or ed_tips.verts[0] == ed_tips_b.verts[1])):
+ middle_vertex_idx = ed_tips.verts[0]
+ tips_to_discard_idx.append(ed_tips.verts[1])
+
+
+ #### List with pairs of verts that belong to the tips of each selection chain (row).
+ verts_tips_same_chain_idx = []
+ if len(all_chains_tips_idx) >= 2:
+ checked_v = []
+ for i in range(0, len(all_chains_tips_idx)):
+ if all_chains_tips_idx[i] not in checked_v:
+ v_chain = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, all_chains_tips_idx[i], middle_vertex_idx)
+
+ verts_tips_same_chain_idx.append([v_chain[0].index, v_chain[len(v_chain) - 1].index])
+
+ checked_v.append(v_chain[0].index)
+ checked_v.append(v_chain[len(v_chain) - 1].index)
+
+
+ #### Selection tips (vertices)
+ verts_tips_parsed_idx = []
+ if len(all_chains_tips_idx) >= 2:
+ for spec_v_idx in all_chains_tips_idx:
+ if (spec_v_idx not in tips_to_discard_idx):
+ verts_tips_parsed_idx.append(spec_v_idx)
+
+
+ #### Identify the type of selection made by the user.
+ if middle_vertex_idx != None:
+ if len(all_chains_tips_idx) == 4: # If there are 4 tips (two selection chains)
+ selection_type = "TWO_CONNECTED"
+ else:
+ # The type of the selection was not identified, so the script stops.
+ return
+ else:
+ if len(all_chains_tips_idx) == 2: # If there are 2 tips (one selection chain)
+ selection_type = "SINGLE"
+ elif len(all_chains_tips_idx) == 4: # If there are 4 tips (two selection chains)
+ selection_type = "TWO_NOT_CONNECTED"
+ elif len(all_chains_tips_idx) == 0:
+ selection_type = "NO_SELECTION"
+ else:
+ # The type of the selection was not identified, so the script stops.
+ return
+
+
+ #### Check if it will be used grease pencil strokes or curves.
+ selected_objs = bpy.context.selected_objects
+ if len(selected_objs) > 1:
+ for ob in selected_objs:
+ if ob != bpy.context.scene.objects.active:
+ ob_gp_strokes = ob
+ using_external_curves = True
+
+ bpy.ops.object.editmode_toggle()
+ else:
+ #### Convert grease pencil strokes to curve.
+ bpy.ops.gpencil.convert(type='CURVE')
+ ob_gp_strokes = bpy.context.object
+ using_external_curves = False
+
+ bpy.ops.object.editmode_toggle()
+
+ ob_gp_strokes.name = "SURFSK_temp_strokes"
+
+ bpy.ops.object.select_name(name = ob_gp_strokes.name)
+ bpy.context.scene.objects.active = bpy.context.scene.objects[ob_gp_strokes.name]
+
+
+ #### If "Keep strokes" is active make a duplicate of the original strokes, which will be intact
+ if bpy.context.scene.SURFSK_keep_strokes:
+ bpy.ops.object.duplicate_move()
+ bpy.context.object.name = "SURFSK_used_strokes"
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.object.editmode_toggle()
+
+ bpy.ops.object.select_name(name = ob_gp_strokes.name)
+ bpy.context.scene.objects.active = bpy.context.scene.objects[ob_gp_strokes.name]
+
+
+ #### Enter editmode for the new curve (converted from grease pencil strokes).
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.object.editmode_toggle()
+
+
+ selection_U_exists = False
+ selection_U2_exists = False
+ selection_V_exists = False
+ selection_V2_exists = False
+ #### Define what vertexes are at the tips of each selection and are not the middle-vertex.
+ if selection_type == "TWO_CONNECTED":
+ selection_U_exists = True
+ selection_V_exists = True
+
+ # Determine which selection is Selection-U and which is Selection-V.
+ points_A = []
+ points_B = []
+ points_first_stroke_tips = []
+
+ points_A.append(self.main_object.data.verts[verts_tips_parsed_idx[0]].co)
+ points_A.append(self.main_object.data.verts[middle_vertex_idx].co)
+
+ points_B.append(self.main_object.data.verts[verts_tips_parsed_idx[1]].co)
+ points_B.append(self.main_object.data.verts[middle_vertex_idx].co)
+
+ points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[0].co)
+ points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co)
+
+ angle_A = self.orientation_difference(points_A, points_first_stroke_tips)
+ angle_B = self.orientation_difference(points_B, points_first_stroke_tips)
+
+ if angle_A < angle_B:
+ first_vert_U_idx = verts_tips_parsed_idx[0]
+ first_vert_V_idx = verts_tips_parsed_idx[1]
+ else:
+ first_vert_U_idx = verts_tips_parsed_idx[1]
+ first_vert_V_idx = verts_tips_parsed_idx[0]
+
+ elif selection_type == "SINGLE" or selection_type == "TWO_NOT_CONNECTED":
+ first_sketched_point_first_stroke_co = ob_gp_strokes.data.splines[0].bezier_points[0].co
+ last_sketched_point_first_stroke_co = ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co
+
+ first_sketched_point_last_stroke_co = ob_gp_strokes.data.splines[len(ob_gp_strokes.data.splines) - 1].bezier_points[0].co
+
+ # The tip of the selected vertices nearest to the first point of the first sketched stroke.
+ prev_dist = 999999999999
+ for i in range(0, len(verts_tips_same_chain_idx)):
+ for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
+ dist = self.pts_distance(first_sketched_point_first_stroke_co, self.main_object.data.verts[verts_tips_same_chain_idx[i][v_idx]].co)
+ if dist < prev_dist:
+ prev_dist = dist
+
+ nearest_tip_first_st_first_pt_idx = i
+
+ nearest_tip_first_pair_first_pt_idx = v_idx
+
+ # Shortest distance to the first point of the first stroke
+ shortest_distance_to_first_stroke = dist
+
+
+ # The tip of the selected vertices nearest to the last point of the first sketched stroke.
+ prev_dist = 999999999999
+ for i in range(0, len(verts_tips_same_chain_idx)):
+ for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
+ dist = self.pts_distance(last_sketched_point_first_stroke_co, self.main_object.data.verts[verts_tips_same_chain_idx[i][v_idx]].co)
+ if dist < prev_dist:
+ prev_dist = dist
+
+ nearest_tip_first_st_last_pt_pair_idx = i
+ nearest_tip_first_st_last_pt_point_idx = v_idx
+
+
+ # The tip of the selected vertices nearest to the first point of the last sketched stroke.
+ prev_dist = 999999999999
+ for i in range(0, len(verts_tips_same_chain_idx)):
+ for v_idx in range(0, len(verts_tips_same_chain_idx[i])):
+ dist = self.pts_distance(first_sketched_point_last_stroke_co, self.main_object.data.verts[verts_tips_same_chain_idx[i][v_idx]].co)
+ if dist < prev_dist:
+ prev_dist = dist
+
+ nearest_tip_last_st_first_pt_pair_idx = i
+ nearest_tip_last_st_first_pt_point_idx = v_idx
+
+
+ points_tips = []
+ points_first_stroke_tips = []
+
+ # Determine if the single selection will be treated as U or as V.
+ edges_sum = 0
+ for i in all_selected_edges_idx:
+ edges_sum += self.pts_distance(self.main_object.data.verts[self.main_object.data.edges[i].verts[0]].co, self.main_object.data.verts[self.main_object.data.edges[i].verts[1]].co)
+
+ average_edge_length = edges_sum / len(all_selected_edges_idx)
+
+
+
+ # If the beginning of the first stroke is near enough to interpret things as an "extrude along strokes" instead of "extrude through strokes"
+ if shortest_distance_to_first_stroke < average_edge_length / 3:
+ selection_U_exists = False
+ selection_V_exists = True
+
+ first_vert_V_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][nearest_tip_first_pair_first_pt_idx]
+
+ if selection_type == "TWO_NOT_CONNECTED":
+ selection_V2_exists = True
+
+ first_vert_V2_idx = verts_tips_same_chain_idx[nearest_tip_first_st_last_pt_pair_idx][nearest_tip_first_st_last_pt_point_idx]
+
+ else:
+ selection_V2_exists = False
+
+ else:
+ selection_U_exists = True
+ selection_V_exists = False
+
+ points_tips.append(self.main_object.data.verts[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]].co)
+ points_tips.append(self.main_object.data.verts[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][1]].co)
+
+ points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[0].co)
+ points_first_stroke_tips.append(ob_gp_strokes.data.splines[0].bezier_points[len(ob_gp_strokes.data.splines[0].bezier_points) - 1].co)
+
+ vec_A = points_tips[0] - points_tips[1]
+ vec_B = points_first_stroke_tips[0] - points_first_stroke_tips[1]
+
+ # Compare the direction of the selection and the first grease pencil stroke to determine which is the "first" vertex of the selection.
+ if vec_A.dot(vec_B) < 0:
+ first_vert_U_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][1]
+ else:
+ first_vert_U_idx = verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]
+
+ if selection_type == "TWO_NOT_CONNECTED":
+ selection_U2_exists = True
+
+ first_vert_U2_idx = verts_tips_same_chain_idx[nearest_tip_last_st_first_pt_pair_idx][nearest_tip_last_st_first_pt_point_idx]
+ else:
+ selection_U2_exists = False
+
+ elif selection_type == "NO_SELECTION":
+ selection_U_exists = False
+ selection_V_exists = False
+
+
+ #### Get an ordered list of the vertices of Selection-U.
+ if selection_U_exists:
+ verts_ordered_U = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U_idx, middle_vertex_idx)
+
+ #### Get an ordered list of the vertices of Selection-U.
+ if selection_U2_exists:
+ verts_ordered_U2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_U2_idx, middle_vertex_idx)
+
+ #### Get an ordered list of the vertices of Selection-V.
+ if selection_V_exists:
+ verts_ordered_V = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V_idx, middle_vertex_idx)
+
+ #### Get an ordered list of the vertices of Selection-U.
+ if selection_V2_exists:
+ verts_ordered_V2 = self.get_ordered_verts(self.main_object, all_selected_edges_idx, all_verts_idx, first_vert_V2_idx, middle_vertex_idx)
+
+
+ #### Calculate edges U proportions.
+
+ # Sum selected edges U lengths.
+ edges_lengths_U = []
+ edges_lengths_sum_U = 0
+
+ if selection_U_exists:
+ edges_lengths_U, edges_lengths_sum_U = self.get_chain_length(verts_ordered_U)
+
+ # Sum selected edges V lengths.
+ edges_lengths_V = []
+ edges_lengths_sum_V = 0
+
+ if selection_V_exists:
+ edges_lengths_V, edges_lengths_sum_V = self.get_chain_length(verts_ordered_V)
+
+ bpy.ops.object.editmode_toggle()
+ for i in range(0, int(bpy.context.scene.SURFSK_precision)):
+ bpy.ops.curve.subdivide()
+ bpy.ops.object.editmode_toggle()
+
+ # Proportions U.
+ edges_proportions_U = []
+ edges_proportions_U = self.get_edges_proportions(edges_lengths_U, edges_lengths_sum_U, selection_U_exists, bpy.context.scene.SURFSK_edges_U)
+ verts_count_U = len(edges_proportions_U) + 1
+
+ # Proportions V.
+ edges_proportions_V = []
+ edges_proportions_V = self.get_edges_proportions(edges_lengths_V, edges_lengths_sum_V, selection_V_exists, bpy.context.scene.SURFSK_edges_V)
+ verts_count_V = len(edges_proportions_V) + 1
+
+
+
+ #### Get ordered lists of points on each sketched curve that mimics the proportions of the edges in the vertex selection.
+ sketched_splines = ob_gp_strokes.data.splines
+ sketched_splines_lengths = []
+ sketched_splines_parsed = []
+ for sp_idx in range(0, len(sketched_splines)):
+ # Calculate spline length
+ sketched_splines_lengths.append(0)
+ for i in range(0, len(sketched_splines[sp_idx].bezier_points)):
+ if i == 0:
+ prev_p = sketched_splines[sp_idx].bezier_points[i]
+ else:
+ p = sketched_splines[sp_idx].bezier_points[i]
+
+ p_difs = [prev_p.co[0] - p.co[0], prev_p.co[1] - p.co[1], prev_p.co[2] - p.co[2]]
+ edge_length = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
+
+ sketched_splines_lengths[sp_idx] += edge_length
+
+ prev_p = p
+
+ # Calculate vertex positions with apropriate edge proportions, and ordered, for each spline.
+ sketched_splines_parsed.append([])
+ partial_spline_length = 0
+ related_edge_U = 0
+ edges_proportions_sum_U = 0
+ edges_lengths_sum_U = 0
+ for i in range(0, len(sketched_splines[sp_idx].bezier_points)):
+ if i == 0:
+ prev_p = sketched_splines[sp_idx].bezier_points[i]
+ sketched_splines_parsed[sp_idx].append(prev_p.co)
+ elif i != len(sketched_splines[sp_idx].bezier_points) - 1:
+ p = sketched_splines[sp_idx].bezier_points[i]
+
+ p_difs = [prev_p.co[0] - p.co[0], prev_p.co[1] - p.co[1], prev_p.co[2] - p.co[2]]
+ edge_length = abs(sqrt(p_difs[0] * p_difs[0] + p_difs[1] * p_difs[1] + p_difs[2] * p_difs[2]))
+
+
+ if edges_proportions_sum_U + edges_proportions_U[related_edge_U] - ((edges_lengths_sum_U + partial_spline_length + edge_length) / sketched_splines_lengths[sp_idx]) > 0: # comparing proportions to see if the proportion in the selection is found in the spline.
+ partial_spline_length += edge_length
+ elif related_edge_U < len(edges_proportions_U) - 1:
+ sketched_splines_parsed[sp_idx].append(prev_p.co)
+
+ edges_proportions_sum_U += edges_proportions_U[related_edge_U]
+ related_edge_U += 1
+
+ edges_lengths_sum_U += partial_spline_length
+ partial_spline_length = edge_length
+
+ prev_p = p
+ else: # last point of the spline for the last edge
+ p = sketched_splines[sp_idx].bezier_points[len(sketched_splines[sp_idx].bezier_points) - 1]
+ sketched_splines_parsed[sp_idx].append(p.co)
+
+
+ #### If the selection type is "TWO_NOT_CONNECTED" replace the last point of each spline with the points in the "target" selection.
+ if selection_type == "TWO_NOT_CONNECTED":
+ if selection_U2_exists:
+ for i in range(0, len(sketched_splines_parsed[len(sketched_splines_parsed) - 1])):
+ sketched_splines_parsed[len(sketched_splines_parsed) - 1][i] = verts_ordered_U2[i].co
+
+
+ #### Create temporary curves along the "control-points" found on the sketched curves and the mesh selection.
+ mesh_ctrl_pts_name = "SURFSK_ctrl_pts"
+ me = bpy.data.meshes.new(mesh_ctrl_pts_name)
+ ob_ctrl_pts = bpy.data.objects.new(mesh_ctrl_pts_name, me)
+ ob_ctrl_pts.data = me
+ bpy.context.scene.objects.link(ob_ctrl_pts)
+
+
+ for i in range(0, verts_count_U):
+ vert_num_in_spline = 1
+
+ if selection_U_exists:
+ ob_ctrl_pts.data.add_geometry(1,0,0)
+ last_v = ob_ctrl_pts.data.verts[len(ob_ctrl_pts.data.verts) - 1]
+ last_v.co = verts_ordered_U[i].co
+
+ vert_num_in_spline += 1
+
+ for sp in sketched_splines_parsed:
+ ob_ctrl_pts.data.add_geometry(1,0,0)
+ v = ob_ctrl_pts.data.verts[len(ob_ctrl_pts.data.verts) - 1]
+ v.co = sp[i]
+
+ if vert_num_in_spline > 1:
+ ob_ctrl_pts.data.add_geometry(0,1,0)
+ ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].verts[0] = len(ob_ctrl_pts.data.verts) - 2
+ ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].verts[1] = len(ob_ctrl_pts.data.verts) - 1
+
+ last_v = v
+
+ vert_num_in_spline += 1
+
+ bpy.ops.object.select_name(name = ob_ctrl_pts.name)
+ bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
+
+
+ # Create curves from control points.
+ bpy.ops.object.convert(target='CURVE', keep_original=False)
+ ob_curves_surf = bpy.context.scene.objects.active
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.curve.spline_type_set(type='BEZIER')
+ bpy.ops.curve.handle_type_set(type='AUTOMATIC')
+ for i in range(0, int(bpy.context.scene.SURFSK_precision)):
+ bpy.ops.curve.subdivide()
+ bpy.ops.object.editmode_toggle()
+
+
+ # Calculate the length of each final surface spline.
+ surface_splines = ob_curves_surf.data.splines
+ surface_splines_lengths = []
+ surface_splines_parsed = []
+ for sp_idx in range(0, len(surface_splines)):
+ # Calculate spline length
+ surface_splines_lengths.append(0)
+ for i in range(0, len(surface_splines[sp_idx].bezier_points)):
+ if i == 0:
+ prev_p = surface_splines[sp_idx].bezier_points[i]
+ else:
+ p = surface_splines[sp_idx].bezier_points[i]
+
+ edge_length = self.pts_distance(prev_p.co, p.co)
+
+ surface_splines_lengths[sp_idx] += edge_length
+
+ prev_p = p
+
+ bpy.ops.object.editmode_toggle()
+ for i in range(0, int(bpy.context.scene.SURFSK_precision)):
+ bpy.ops.curve.subdivide()
+ bpy.ops.object.editmode_toggle()
+
+ for sp_idx in range(0, len(surface_splines)):
+ # Calculate vertex positions with apropriate edge proportions, and ordered, for each spline.
+ surface_splines_parsed.append([])
+ partial_spline_length = 0
+ related_edge_V = 0
+ edges_proportions_sum_V = 0
+ edges_lengths_sum_V = 0
+ for i in range(0, len(surface_splines[sp_idx].bezier_points)):
+ if i == 0:
+ prev_p = surface_splines[sp_idx].bezier_points[i]
+ surface_splines_parsed[sp_idx].append(prev_p.co)
+ elif i != len(surface_splines[sp_idx].bezier_points) - 1:
+ p = surface_splines[sp_idx].bezier_points[i]
+
+ edge_length = self.pts_distance(prev_p.co, p.co)
+
+ if edges_proportions_sum_V + edges_proportions_V[related_edge_V] - ((edges_lengths_sum_V + partial_spline_length + edge_length) / surface_splines_lengths[sp_idx]) > 0: # comparing proportions to see if the proportion in the selection is found in the spline.
+ partial_spline_length += edge_length
+ elif related_edge_V < len(edges_proportions_V) - 1:
+ surface_splines_parsed[sp_idx].append(prev_p.co)
+
+ edges_proportions_sum_V += edges_proportions_V[related_edge_V]
+ related_edge_V += 1
+
+ edges_lengths_sum_V += partial_spline_length
+ partial_spline_length = edge_length
+
+ prev_p = p
+ else: # last point of the spline for the last edge
+ p = surface_splines[sp_idx].bezier_points[len(surface_splines[sp_idx].bezier_points) - 1]
+ surface_splines_parsed[sp_idx].append(p.co)
+
+ # Set the first and last verts of each spline to the locations of the respective verts in the selections.
+ if selection_V_exists:
+ for i in range(0, len(surface_splines_parsed[0])):
+ surface_splines_parsed[len(surface_splines_parsed) - 1][i] = verts_ordered_V[i].co
+
+ if selection_type == "TWO_NOT_CONNECTED":
+ if selection_V2_exists:
+ for i in range(0, len(surface_splines_parsed[0])):
+ surface_splines_parsed[0][i] = verts_ordered_V2[i].co
+
+
+ #### Delete object with control points and object from grease pencil convertion.
+ bpy.ops.object.select_name(name = ob_ctrl_pts.name)
+ bpy.context.scene.objects.active = bpy.data.objects[ob_ctrl_pts.name]
+ bpy.ops.object.delete()
+
+ bpy.ops.object.select_name(name = ob_gp_strokes.name)
+ bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
+ bpy.ops.object.delete()
+
+
+
+ #### Generate surface.
+
+ # Get all verts coords.
+ all_surface_verts_co = []
+ for i in range(0, len(surface_splines_parsed)):
+ # Get coords of all verts and make a list with them
+ for pt_co in surface_splines_parsed[i]:
+ all_surface_verts_co.append(pt_co)
+
+
+ # Define verts for each face.
+ all_surface_faces = []
+ for i in range(0, len(all_surface_verts_co) - len(surface_splines_parsed[0])):
+ if ((i + 1) / len(surface_splines_parsed[0]) != int((i + 1) / len(surface_splines_parsed[0]))):
+ all_surface_faces.append([i+1, i , i + len(surface_splines_parsed[0]), i + len(surface_splines_parsed[0]) + 1])
+
+
+ # Build the mesh.
+ surf_me_name = "SURFSK_surface"
+ me_surf = bpy.data.meshes.new(surf_me_name)
+
+ me_surf.from_pydata(all_surface_verts_co, [], all_surface_faces)
+
+ me_surf.update()
+
+ ob_surface = bpy.data.objects.new(surf_me_name, me_surf)
+ bpy.context.scene.objects.link(ob_surface)
+
+
+ #### Join the new mesh to the main object.
+ ob_surface.select = True
+ self.main_object.select = True
+ bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
+ bpy.ops.object.join()
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.remove_doubles(limit=0.0001)
+ bpy.ops.mesh.normals_make_consistent(inside=False)
+ bpy.ops.mesh.select_all(action='DESELECT')
+
+ #### Delete grease pencil strokes
+ bpy.ops.gpencil.active_frame_delete()
+
+
+ def invoke (self, context, event):
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.object.editmode_toggle()
+ self.main_object = bpy.context.scene.objects.active
+
+ self.execute(context)
+
+ return {"FINISHED"}
+
+
+
+
+class GPENCIL_OT_SURFSK_strokes_to_curves(bpy.types.Operator):
+ bl_idname = "GPENCIL_OT_SURFSK_strokes_to_curves"
+ bl_label = "Convert grease pencil strokes into curves and enter edit mode"
+ bl_description = "Convert grease pencil strokes into curves and enter edit mode"
+
+
+ def execute(self, context):
+ #### Convert grease pencil strokes to curve.
+ bpy.ops.gpencil.convert(type='CURVE')
+ ob_gp_strokes = bpy.context.object
+ ob_gp_strokes.name = "SURFSK_strokes"
+
+ #### Delete grease pencil strokes.
+ bpy.ops.object.select_name(name = self.main_object.name)
+ bpy.context.scene.objects.active = bpy.data.objects[self.main_object.name]
+ bpy.ops.gpencil.active_frame_delete()
+
+
+ bpy.ops.object.select_name(name = ob_gp_strokes.name)
+ bpy.context.scene.objects.active = bpy.data.objects[ob_gp_strokes.name]
+
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+ bpy.ops.curve.smooth()
+
+ curve_crv = ob_gp_strokes.data
+ bpy.ops.curve.spline_type_set(type="BEZIER")
+ bpy.ops.curve.handle_type_set(type="AUTOMATIC")
+ bpy.data.curves[curve_crv.name].draw_handles = False
+ bpy.data.curves[curve_crv.name].draw_normals = False
+
+
+ def invoke (self, context, event):
+ self.main_object = bpy.context.object
+
+
+ self.execute(context)
+
+ return {"FINISHED"}
+
+
+
+
+
+def register():
+ bpy.types.register(GPENCIL_OT_SURFSK_add_surface)
+ bpy.types.register(GPENCIL_OT_SURFSK_strokes_to_curves)
+ bpy.types.register(VIEW3D_PT_tools_SURF_SKETCH)
+
+ keymap_item_add_surf = bpy.data.window_managers[0].active_keyconfig.keymaps["3D View"].items.add("GPENCIL_OT_SURFSK_add_surface","E","PRESS", key_modifier="D")
+ keymap_item_stroke_to_curve = bpy.data.window_managers[0].active_keyconfig.keymaps["3D View"].items.add("GPENCIL_OT_SURFSK_strokes_to_curves","C","PRESS", key_modifier="D")
+
+def unregister():
+ bpy.types.unregister(GPENCIL_OT_SURFSK_add_surface)
+ bpy.types.unregister(GPENCIL_OT_SURFSK_strokes_to_curves)
+ bpy.types.unregister(VIEW3D_PT_tools_SURF_SKETCH)
+ km = bpy.data.window_managers[0].active_keyconfig.keymaps["3D View"]
+ for kmi in km.items:
+ if kmi.idname == 'wm.call_menu':
+ if kmi.properties.name == "GPENCIL_OT_SURFSK_add_surface":
+ km.remove_item(kmi)
+ elif kmi.properties.name == "GPENCIL_OT_SURFSK_strokes_to_curves":
+ km.remove_item(kmi)
+ else:
+ continue
+
+
+if __name__ == "__main__":
+ register()
+
diff --git a/object_add_chain.py b/object_add_chain.py
new file mode 100644
index 00000000..40782725
--- /dev/null
+++ b/object_add_chain.py
@@ -0,0 +1,156 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+bl_addon_info = {
+ 'name': 'Object: Add Chain',
+ 'author': 'Brian Hinton (Nichod)',
+ 'version': '0.1',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Add > Mesh > Chain',
+ 'description': 'Adds Chain with curve guide for easy creation',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Object/Add_Chain',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22203&group_id=153&atid=469',
+ 'category': 'Object'}
+
+
+import bpy
+
+def Add_Chain():
+
+
+ ##Adds Empty to scene
+ bpy.ops.object.add(type='EMPTY', view_align=False, enter_editmode=False, location=(0, 0, 0),
+rotation=(0, 0, 0), layer=(True, False, False, False, False, False,
+False, False, False, False, False, False, False, False, False, False, False, False, False,
+False))
+
+ ##Changes name of Empty to rot_link adds variable emp
+ emp = bpy.context.object
+ emp.name = "rot_link"
+
+ ##Rotate emp ~ 90 degrees
+ emp.rotation_euler = [1.570796, 0, 0]
+
+ ##Adds Curve Path to scene
+ bpy.ops.curve.primitive_nurbs_path_add( view_align=False, enter_editmode=False, location=(0, 0, 0),
+rotation=(0, 0, 0), layer=(True, False, False, False, False, False,
+False, False, False, False, False, False, False, False, False, False, False, False, False,
+False))
+
+ ##Change Curve name to deform adds variable curv
+ curv = bpy.context.object
+ curv.name = "deform"
+
+ ##Inserts Torus primitive
+ bpy.ops.mesh.primitive_torus_add(major_radius=1, minor_radius=0.25,
+major_segments=12, minor_segments=4, use_abso=False, abso_major_rad=1,
+abso_minor_rad=0.5)
+
+ ##Positions Torus primitive to center of scene
+ bpy.context.active_object.location = [0, 0, 0]
+
+ ##Changes Torus name to chain adds variable tor
+ tor = bpy.context.object
+ tor.name = "chain"
+
+ ##Adds Array Modifier to tor
+ bpy.ops.object.modifier_add(type='ARRAY')
+
+ ##Adds subsurf modifier tor
+ bpy.ops.object.modifier_add(type='SUBSURF')
+
+ ##Smooths tor
+ bpy.ops.object.shade_smooth()
+
+ ##Select curv
+ sce = bpy.context.scene
+ sce.objects.active = curv
+
+ ##Toggle into editmode
+ bpy.ops.object.editmode_toggle()
+
+ ##Translate curve object
+ bpy.ops.transform.translate(value=(2, 0, 0), constraint_axis=(True, False, False),
+constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED',
+proportional_editing_falloff='SMOOTH', proportional_size=1, snap=False,
+snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0),
+release_confirm=False)
+
+ ##Toggle into objectmode
+ bpy.ops.object.editmode_toggle()
+
+ ##Select tor or chain
+ sce.objects.active = tor
+
+ ##Selects Array Modifier for editing
+ array = tor.modifiers['Array']
+
+ ##Change Array Modifier Parameters
+ array.fit_type = ('FIT_CURVE')
+ array.curve = curv
+ array.offset_object = emp
+ array.add_offset_object = True
+ array.relative_offset_displacement = [ 0.549, 0, 0 ]
+
+ ##Add curve modifier
+ bpy.ops.object.modifier_add(type='CURVE')
+
+ ##Selects Curve Modifier for editing
+ cur = tor.modifiers['Curve']
+
+ ##Change Curve Modifier Parameters
+ cur.object = curv
+
+#makes AddChain an operator
+class AddChain(bpy.types.Operator):
+ '''Add a Chain.'''
+ bl_idname = "mesh.primitive_chain_add"
+ bl_label = "Add Chain"
+ bl_options = {'REGISTER', 'UNDO'}
+
+
+ def execute(self, context):
+ Add_Chain()
+
+ return {'FINISHED'}
+
+# Register the operator
+menu_func = (lambda self,
+ context: self.layout.operator(AddChain.bl_idname,
+ text="Chain", icon='PLUGIN'))
+
+
+def register():
+ bpy.types.register(AddChain)
+
+ # Add "Chain" menu to the "Add Mesh" menu.
+ bpy.types.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ bpy.types.unregister(AddChain)
+
+ # Remove "Chain" menu from the "Add Mesh" menu.
+ bpy.types.INFO_MT_mesh_add.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/object_cloud_gen.py b/object_cloud_gen.py
new file mode 100644
index 00000000..8b9f0e1f
--- /dev/null
+++ b/object_cloud_gen.py
@@ -0,0 +1,679 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Place this file in the .blender/scripts/addons dir
+You have to activated the script in the "Add-Ons" tab (user preferences).
+The functionality can then be accessed via the Tool shelf when objects
+are selected
+
+Rev 0 initial release
+Rev 0.1 added scene to create_mesh per python api change.
+Rev 0.2 Added Point Density turbulence and fixed degenerate
+Rev 0.3 Fixed bug in degenerate
+Rev 0.4 updated for api change/changed to new apply modifier technique
+Rev 0.5 made particle count equation with radius so radius increases with cloud volume
+Rev 0.6 added poll function to operator, fixing crash with no selected objects
+Rev 0.7 added particles option and Type of Cloud wanted selector
+"""
+
+
+bl_addon_info = {
+ 'name': 'Object: Cloud Generator',
+ 'author': 'Nick Keeline(nrk)',
+ 'version': '0.7',
+ 'blender': (2, 5, 3),
+ 'location': 'Tool Shelf ',
+ 'description': 'Creates Volumetric Clouds',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Object/Cloud_Gen',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22015&group_id=153&atid=469',
+ 'category': 'Object'}
+
+
+import bpy
+import mathutils
+from math import *
+from bpy.props import *
+
+
+# This routine takes an object and deletes all of the geometry in it
+# and adds a bounding box to it.
+# It will add or subtract the bound box size by the variable sizeDifference.
+def makeObjectIntoBoundBox(object, sizeDifference):
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Select the object
+ object.select = True
+
+ # Go into Edit Mode
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ mesh = object.data
+ verts = mesh.verts
+
+ #Set the max and min verts to the first vertex on the list
+ maxVert = [verts[0].co[0], verts[0].co[1], verts[0].co[2]]
+ minVert = [verts[0].co[0], verts[0].co[1], verts[0].co[2]]
+
+ #Create Max and Min Vertex array for the outer corners of the box
+ for vert in verts:
+ #Max vertex
+ if vert.co[0] > maxVert[0]:
+ maxVert[0] = vert.co[0]
+ if vert.co[1] > maxVert[1]:
+ maxVert[1] = vert.co[1]
+ if vert.co[2] > maxVert[2]:
+ maxVert[2] = vert.co[2]
+
+ #Min Vertex
+ if vert.co[0] < minVert[0]:
+ minVert[0] = vert.co[0]
+ if vert.co[1] < minVert[1]:
+ minVert[1] = vert.co[1]
+ if vert.co[2] < minVert[2]:
+ minVert[2] = vert.co[2]
+
+ #Add the size difference to the max size of the box
+ maxVert[0] = maxVert[0] + sizeDifference
+ maxVert[1] = maxVert[1] + sizeDifference
+ maxVert[2] = maxVert[2] + sizeDifference
+
+ #subtract the size difference to the min size of the box
+ minVert[0] = minVert[0] - sizeDifference
+ minVert[1] = minVert[1] - sizeDifference
+ minVert[2] = minVert[2] - sizeDifference
+
+ #Create arrays of verts and faces to be added to the mesh
+ addVerts = []
+
+ #X high loop
+ addVerts.append([maxVert[0], maxVert[1], maxVert[2]])
+ addVerts.append([maxVert[0], maxVert[1], minVert[2]])
+ addVerts.append([maxVert[0], minVert[1], minVert[2]])
+ addVerts.append([maxVert[0], minVert[1], maxVert[2]])
+
+ #x low loop
+ addVerts.append([minVert[0], maxVert[1], maxVert[2]])
+ addVerts.append([minVert[0], maxVert[1], minVert[2]])
+ addVerts.append([minVert[0], minVert[1], minVert[2]])
+ addVerts.append([minVert[0], minVert[1], maxVert[2]])
+
+ # Make the faces of the bounding box.
+ addFaces = []
+
+ # Draw a box on paper and number the vertices.
+ # Use right hand rule to come up with number orders for faces on
+ # the box (with normals pointing out).
+ addFaces.append([0, 3, 2, 1])
+ addFaces.append([4, 5, 6, 7])
+ addFaces.append([0, 1, 5, 4])
+ addFaces.append([1, 2, 6, 5])
+ addFaces.append([2, 3, 7, 6])
+ addFaces.append([0, 4, 7, 3])
+
+ # Delete all geometry from the object.
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.delete(type='ALL')
+
+ # Must be in object mode for from_pydata to work
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Add the mesh data.
+ mesh.from_pydata(addVerts, [], addFaces)
+
+ # Update the mesh
+ mesh.update()
+
+
+def applyScaleRotLoc(scene, obj):
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Select the object
+ obj.select = True
+ scene.objects.active = obj
+
+ #bpy.ops.object.rotation_apply()
+ bpy.ops.object.location_apply()
+ bpy.ops.object.scale_apply()
+
+
+def totallyDeleteObject(scene, obj):
+ #To Do this section to be updated when
+ #Ability to completely delet objects added to blender
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Select the object and delete it.
+ obj.select = True
+ scene.objects.active = obj
+
+ # Delete all material slots in obj
+ for i in range(len(obj.material_slots)):
+ #textureSlots = cloudMaterial.texture_slots
+ obj.active_material_index = i - 1
+ bpy.ops.object.material_slot_remove()
+
+ #bpy.ops.object.parent_clear(type='CLEAR')
+
+ # Delete the Main Object
+ bpy.ops.object.delete()
+ #bpy.data.objects.remove(obj)
+
+
+def makeParent(parentobj, childobj, scene):
+
+ applyScaleRotLoc(scene, parentobj)
+
+ applyScaleRotLoc(scene, childobj)
+
+ childobj.parent = parentobj
+
+ #childobj.location = childobj.location - parentobj.location
+
+
+def addNewObject(scene, name, copyobj):
+ '''
+ Add an object and do other silly stuff.
+ '''
+ # Create new mesh
+ mesh = bpy.data.meshes.new(name)
+
+ # Create a new object.
+ ob_new = bpy.data.objects.new(name, mesh)
+ tempme = copyobj.data
+ ob_new.data = tempme.copy()
+ ob_new.scale = copyobj.scale
+ ob_new.location = copyobj.location
+
+ # Link new object to the given scene and select it.
+ scene.objects.link(ob_new)
+ ob_new.select = True
+
+ return ob_new
+
+
+def combineObjects(scene, combined, listobjs):
+ # scene is the current scene
+ # combined is the object we want to combine everything into
+ # listobjs is the list of objects to stick into combined
+
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Select the new object.
+ combined.select = True
+ scene.objects.active = combined
+
+ # Add data
+ if (len(listobjs) > 0):
+ for i in listobjs:
+ # Add a modifier
+ bpy.ops.object.modifier_add(type='BOOLEAN')
+
+ union = combined.modifiers
+ union[0].name = "AddEmUp"
+ union[0].object = i
+ union[0].operation = 'UNION'
+
+ # Apply modifier
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier=union[0].name)
+
+
+# Returns True if we want to degenerate
+# and False if we want to generate a new cloud.
+def degenerateCloud(obj):
+ if not obj:
+ return False
+
+ if "CloudMember" in obj:
+ if obj["CloudMember"] != None:
+ if obj.parent:
+ if "CloudMember" not in obj.parent:
+ return False
+
+ else:
+ return True
+
+ return False
+
+class View3DPanel(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+
+
+class VIEW3D_PT_tools_cloud(View3DPanel):
+ bl_label = "Cloud Generator"
+ bl_context = "objectmode"
+
+ def draw(self, context):
+ active_obj = context.active_object
+ layout = self.layout
+ col = layout.column(align=True)
+
+ degenerate = degenerateCloud(active_obj)
+
+ if active_obj and degenerate:
+
+ col.operator("cloud.generate_cloud", text="DeGenerate")
+
+ elif active_obj is None:
+
+ col.label(text="Select one or more")
+ col.label(text="objects to generate")
+ col.label(text="a cloud.")
+
+ elif "CloudMember" in active_obj:
+
+ col.label(text="Must select")
+ col.label(text="bound box")
+
+ elif active_obj and active_obj.type == 'MESH':
+
+ col.operator("cloud.generate_cloud", text="Generate Cloud")
+
+ col.prop(context.scene, "cloudparticles")
+ col.prop(context.scene, "cloud_type")
+ else:
+ col.label(text="Select one or more")
+ col.label(text="objects to generate")
+ col.label(text="a cloud.")
+
+cloudTypes = []
+
+cloudTypes.append(("0","Stratus","Generate Stratus_foggy Cloud"))
+cloudTypes.append(("1","Cumulous","Generate Cumulous_puffy Cloud"))
+cloudTypes.append(("2","Cirrus","Generate Cirrus_wispy Cloud"))
+#cloudTypes.append(("3","Nimbus","Generate Nimbus Cloud"))
+
+
+bpy.types.Scene.BoolProperty( attr="cloudparticles",
+ name="Particles",
+ description="Generate Cloud as Particle System",
+ default=False)
+
+bpy.types.Scene.EnumProperty( attr="cloud_type",
+ name="Type",
+ description="Select the type of cloud to create with material settings",
+ items = cloudTypes, default = '0')
+
+class GenerateCloud(bpy.types.Operator):
+ bl_idname = "cloud.generate_cloud"
+ bl_label = "Generate Cloud"
+ bl_description = "Create a Cloud."
+ bl_register = True
+ bl_undo = True
+
+ def poll(self, context):
+ if not context.active_object:
+ return False
+ else:
+ return (context.active_object.type=='MESH')
+
+ def execute(self, context):
+ # Make variable that is the current .blend file main data blocks
+ main = context.main
+
+ # Make variable that is the active object selected by user
+ active_object = context.active_object
+
+ # Make variable scene that is current scene
+ scene = context.scene
+
+ # Parameters the user may want to change:
+ # Number of points this number is multiplied by the volume to get
+ # the number of points the scripts will put in the volume.
+ numOfPoints = 1.0
+ maxNumOfPoints = 100000
+ scattering = 2.5
+ pointDensityRadiusFactor = 1.0
+ densityScale = 1.5
+
+ # Should we degnerate?
+ degenerate = degenerateCloud(active_object)
+
+ if degenerate:
+ # Degenerate Cloud
+ mainObj = active_object
+
+ cloudMembers = active_object.children
+
+ createdObjects = []
+ definitionObjects = []
+ for member in cloudMembers:
+ applyScaleRotLoc(scene, member)
+ if (member["CloudMember"] == "CreatedObj"):
+ createdObjects.append(member)
+ else:
+ definitionObjects.append(member)
+
+ for defObj in definitionObjects:
+ #Delete cloudmember data from objects
+ if "CloudMember" in defObj:
+ del(defObj["CloudMember"])
+
+ for createdObj in createdObjects:
+ totallyDeleteObject(scene, createdObj)
+
+ # Delete the main object
+ totallyDeleteObject(scene, mainObj)
+
+ # Select all of the left over boxes so people can immediately
+ # press generate again if they want.
+ for eachMember in definitionObjects:
+ eachMember.max_draw_type = 'SOLID'
+ eachMember.select = True
+ #scene.objects.active = eachMember
+
+ #TODO Delete this when render bug caused by degenerate is fixed.
+ self.report({'WARNING'}, "Please save file exit and reenter blender before rendering to clean memory and prevent crash")
+
+ else:
+ # Generate Cloud
+
+ ###############Create Combined Object bounds##################
+ # Make a list of all Selected objects.
+ selectedObjects = bpy.context.selected_objects
+ if not selectedObjects:
+ selectedObjects = [bpy.context.active_object]
+
+ # Create a new object bounds
+ bounds = addNewObject(scene,
+ "CloudBounds",
+ selectedObjects[0])
+
+ bounds.max_draw_type = 'BOUNDS'
+ bounds.hide_render = False
+
+ # Just add a Definition Property designating this
+ # as the main object.
+ bounds["CloudMember"] = "MainObj"
+
+ # Since we used iteration 0 to copy with object we
+ # delete it off the list.
+ firstObject = selectedObjects[0]
+ del selectedObjects[0]
+
+ # Apply location Rotation and Scale to all objects involved.
+ applyScaleRotLoc(scene, bounds)
+ for each in selectedObjects:
+ applyScaleRotLoc(scene, each)
+
+ # Let's combine all of them together.
+ combineObjects(scene, bounds, selectedObjects)
+
+ # Let's add some property info to the objects.
+ for selObj in selectedObjects:
+ selObj["CloudMember"] = "DefinitioinObj"
+ selObj.name = "DefinitioinObj"
+ selObj.max_draw_type = 'WIRE'
+ selObj.hide_render = True
+ makeParent(bounds, selObj, scene)
+
+ # Do the same to the 1. object since it is no longer in list.
+ firstObject["CloudMember"] = "DefinitioinObj"
+ firstObject.name = "DefinitioinObj"
+ firstObject.max_draw_type = 'WIRE'
+ firstObject.hide_render = True
+ makeParent(bounds, firstObject, scene)
+
+ ###############Create Cloud for putting Cloud Mesh############
+ # Create a new object cloud.
+ cloud = addNewObject(scene, "CloudMesh", bounds)
+ cloud["CloudMember"] = "CreatedObj"
+ cloud.max_draw_type = 'WIRE'
+ cloud.hide_render = True
+
+ makeParent(bounds, cloud, scene)
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.subdivide(number_cuts=2, fractal=0, smoothness=1)
+ bpy.ops.object.location_apply()
+ bpy.ops.mesh.vertices_smooth(repeat=20)
+ bpy.ops.mesh.tris_convert_to_quads()
+ bpy.ops.mesh.faces_shade_smooth()
+ bpy.ops.object.editmode_toggle()
+
+ ###############Create Particles in cloud obj##################
+ # Turn off gravity.
+ scene.use_gravity = False
+
+ # Set time to 0.
+ scene.frame_current = 0
+
+ # Add a new particle system.
+ bpy.ops.object.particle_system_add()
+
+ #Particle settings setting it up!
+ cloudParticles = cloud.active_particle_system
+ cloudParticles.name = "CloudParticles"
+ cloudParticles.settings.frame_start = 0
+ cloudParticles.settings.frame_end = 0
+ cloudParticles.settings.emit_from = 'VOLUME'
+ cloudParticles.settings.draw_as = 'DOT'
+ cloudParticles.settings.ren_as = 'NONE'
+ cloudParticles.settings.normal_factor = 0
+ cloudParticles.settings.distribution = 'RAND'
+ cloudParticles.settings.physics_type = 'NO'
+
+ ####################Create Volume Material####################
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Select the object.
+ bounds.select = True
+ scene.objects.active = bounds
+
+ # Turn bounds object into a box.
+ makeObjectIntoBoundBox(bounds, .6)
+
+ # Delete all material slots in bounds object.
+ for i in range(len(bounds.material_slots)):
+ bounds.active_material_index = i - 1
+ bpy.ops.object.material_slot_remove()
+
+ # Add a new material.
+ cloudMaterial = main.materials.new("CloudMaterial")
+ bpy.ops.object.material_slot_add()
+ bounds.material_slots[0].material = cloudMaterial
+
+ # Set Up the Cloud Material
+ cloudMaterial.name = "CloudMaterial"
+ cloudMaterial.type = 'VOLUME'
+ mVolume = cloudMaterial.volume
+ mVolume.scattering = scattering
+ mVolume.density = 0
+ mVolume.density_scale = densityScale
+ mVolume.transmission_color = [3, 3, 3]
+ mVolume.step_size = 0.1
+ mVolume.light_cache = True
+ mVolume.cache_resolution = 75
+
+ # Add a texture
+ vMaterialTextureSlots = cloudMaterial.texture_slots
+ cloudtex = main.textures.new("CloudTex")
+ cloudMaterial.add_texture(cloudtex, 'ORCO')
+ cloudtex.type = 'CLOUDS'
+ cloudtex.noise_type = 'HARD_NOISE'
+ cloudtex.noise_size = 2
+
+ # Add a force field to the points.
+ cloudField = bounds.field
+ cloudField.type = 'TEXTURE'
+ cloudField.strength = 2
+ cloudField.texture = cloudtex
+
+ # Set time
+ #for i in range(12):
+ # scene.current_frame = i
+ # scene.update()
+ scene.frame_current = 1
+
+ #bpy.ops.ptcache.bake(bake=False)
+
+ # Add a Point Density texture
+ cloudPointDensity = main.textures.new("CloudPointDensity")
+ cloudPointDensity.type = 'POINT_DENSITY'
+ cloudMaterial.add_texture(cloudPointDensity, 'ORCO')
+ pDensity = vMaterialTextureSlots[1].texture
+ vMaterialTextureSlots[1].map_density = True
+ vMaterialTextureSlots[1].rgb_to_intensity = True
+ vMaterialTextureSlots[1].texture_coordinates = 'GLOBAL'
+ pDensity.pointdensity.vertices_cache = 'WORLD_SPACE'
+ pDensity.pointdensity.turbulence = True
+ pDensity.pointdensity.noise_basis = 'VORONOI_F2'
+ pDensity.pointdensity.turbulence_depth = 3
+
+ pDensity.use_color_ramp = True
+ pRamp = pDensity.color_ramp
+ pRamp.interpolation = 'LINEAR'
+ pRampElements = pRamp.elements
+ #pRampElements[1].position = .9
+ #pRampElements[1].color = [.18,.18,.18,.8]
+ bpy.ops.texture.slot_move(type='UP')
+
+
+ # Estimate the number of particles for the size of bounds.
+ volumeBoundBox = (bounds.dimensions[0] * bounds.dimensions[1]* bounds.dimensions[2])
+ numParticles = int((2.4462 * volumeBoundBox + 430.4) * numOfPoints)
+ if numParticles > maxNumOfPoints:
+ numParticles = maxNumOfPoints
+ if numParticles < 10000:
+ numParticles = int(numParticles + 15 * volumeBoundBox)
+ print(numParticles)
+
+ # Set the number of particles according to the volume
+ # of bounds.
+ cloudParticles.settings.amount = numParticles
+
+ pDensity.pointdensity.radius = (.00013764 * volumeBoundBox + .3989) * pointDensityRadiusFactor
+
+ # Set time to 1.
+ scene.frame_current = 1
+
+ if not scene.cloudparticles:
+ ###############Create CloudPnts for putting points in#########
+ # Create a new object cloudPnts
+ cloudPnts = addNewObject(scene, "CloudPoints", bounds)
+ cloudPnts["CloudMember"] = "CreatedObj"
+ cloudPnts.max_draw_type = 'WIRE'
+ cloudPnts.hide_render = True
+
+ makeParent(bounds, cloudPnts, scene)
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.select_all(action='SELECT')
+
+ bpy.ops.mesh.delete(type='ALL')
+
+ meshPnts = cloudPnts.data
+
+ listCloudParticles = cloudParticles.particles
+
+ listMeshPnts = []
+ for pTicle in listCloudParticles:
+ listMeshPnts.append(pTicle.location)
+
+ # Must be in object mode fro from_pydata to work.
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Add in the mesh data.
+ meshPnts.from_pydata(listMeshPnts, [], [])
+
+ # Update the mesh.
+ meshPnts.update()
+
+ # Add a modifier.
+ bpy.ops.object.modifier_add(type='DISPLACE')
+
+ cldPntsModifiers = cloudPnts.modifiers
+ cldPntsModifiers[0].name = "CloudPnts"
+ cldPntsModifiers[0].texture = cloudtex
+ cldPntsModifiers[0].texture_coordinates = 'OBJECT'
+ cldPntsModifiers[0].texture_coordinate_object = cloud
+ cldPntsModifiers[0].strength = -1.4
+
+ # Apply modifier
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier=cldPntsModifiers[0].name)
+
+ pDensity.pointdensity.point_source = 'OBJECT'
+ pDensity.pointdensity.object = cloudPnts
+
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Select the object.
+ cloud.select = True
+ scene.objects.active = cloud
+
+ bpy.ops.object.particle_system_remove()
+
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+ else:
+
+ pDensity.pointdensity.point_source = 'PARTICLE_SYSTEM'
+ pDensity.pointdensity.object = cloud
+ pDensity.pointdensity.particle_system = cloudParticles
+
+ if scene.cloud_type == '1': # Cumulous
+ print("Cumulous")
+ mVolume.density_scale = 2.22
+ pDensity.pointdensity.turbulence_depth = 10
+ pDensity.pointdensity.turbulence_strength = 6.3
+ pDensity.pointdensity.turbulence_size = 2.9
+ pRampElements[1].position = .606
+ pDensity.pointdensity.radius = pDensity.pointdensity.radius + .1
+
+ elif scene.cloud_type == '2': # Cirrus
+ print("Cirrus")
+ pDensity.pointdensity.turbulence_strength = 22
+ mVolume.transmission_color = [3.5, 3.5, 3.5]
+ mVolume.scattering = .13
+
+ # Select the object.
+ bounds.select = True
+ scene.objects.active = bounds
+
+ return {'FINISHED'}
+
+
+classes = [VIEW3D_PT_tools_cloud,
+ GenerateCloud]
+
+
+def register():
+ register = bpy.types.register
+ for cls in classes:
+ register(cls)
+
+
+def unregister():
+ unregister = bpy.types.unregister
+ for cls in classes:
+ unregister(cls)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/object_fracture/__init__.py b/object_fracture/__init__.py
new file mode 100644
index 00000000..f1c87d6a
--- /dev/null
+++ b/object_fracture/__init__.py
@@ -0,0 +1,86 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_addon_info = {
+ 'name': 'Object: Fracture Tools',
+ 'author': 'pildanovak',
+ 'version': '2.0',
+ 'blender': (2, 5, 3),
+ 'location': 'Fracture tools (Search > Fracture Object & ,' \
+ 'Add -> Fracture Helper Objects',
+ 'description': 'Fractured Object, Bomb, Projectile, Recorder',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Object/Fracture',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21793&group_id=153&atid=469',
+ 'category': 'Object'}
+
+
+import bpy
+from object_fracture import fracture_ops, fracture_setup
+
+
+class INFO_MT_add_fracture_objects(bpy.types.Menu):
+ bl_idname = "INFO_MT_add_fracture_objects"
+ bl_label = "Fracture Helper Objects"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ layout.operator("object.import_fracture_bomb",
+ text="Bomb")
+ layout.operator("object.import_fracture_projectile",
+ text="Projectile")
+ layout.operator("object.import_fracture_recorder",
+ text="Rigidbody Recorder")
+
+import space_info
+# Define the submenu
+menu_func = (lambda self,
+ context: self.layout.menu("INFO_MT_add_fracture_objects", icon="PLUGIN"))
+
+
+def register():
+ bpy.types.register(fracture_ops.FractureSimple)
+ bpy.types.register(fracture_ops.FractureGroup)
+ bpy.types.register(fracture_ops.ImportFractureRecorder)
+ bpy.types.register(fracture_ops.ImportFractureBomb)
+ bpy.types.register(fracture_ops.ImportFractureProjectile)
+ bpy.types.register(fracture_setup.SetupFractureShards)
+ bpy.types.register(INFO_MT_add_fracture_objects)
+
+ # Add the "add fracture objects" menu to the "Add" menu
+ space_info.INFO_MT_add.append(menu_func)
+
+
+def unregister():
+ bpy.types.unregister(fracture_ops.FractureSimple)
+ bpy.types.unregister(fracture_ops.FractureGroup)
+ bpy.types.unregister(fracture_ops.ImportFractureRecorder)
+ bpy.types.unregister(fracture_ops.ImportFractureBomb)
+ bpy.types.unregister(fracture_ops.ImportFractureProjectile)
+ bpy.types.unregister(fracture_setup.SetupFractureShards)
+ bpy.types.unregister(INFO_MT_add_fracture_objects)
+
+ # Remove "add fracture objects" menu from the "Add" menu.
+ space_info.INFO_MT_add.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/object_fracture/data.blend b/object_fracture/data.blend
new file mode 100644
index 00000000..e0e72dcf
--- /dev/null
+++ b/object_fracture/data.blend
Binary files differ
diff --git a/object_fracture/fracture_ops.py b/object_fracture/fracture_ops.py
new file mode 100644
index 00000000..b2aa8442
--- /dev/null
+++ b/object_fracture/fracture_ops.py
@@ -0,0 +1,492 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+from bpy.props import *
+import os
+import random
+import mathutils
+from mathutils import *
+
+
+def create_cutter(context, crack_type, scale, roughness):
+ ncuts = 12
+ if crack_type == 'FLAT' or crack_type == 'FLAT_ROUGH':
+ bpy.ops.mesh.primitive_cube_add(
+ view_align=False,
+ enter_editmode=False,
+ location=(0, 0, 0),
+ rotation=(0, 0, 0),
+ layer=(True, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False))
+
+ for v in context.scene.objects.active.data.verts:
+ v.co[0] += 1
+ v.co[0] *= scale
+ v.co[1] *= scale
+ v.co[2] *= scale
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.faces_shade_smooth()
+ bpy.ops.uv.reset()
+
+ if crack_type == 'FLAT_ROUGH':
+ bpy.ops.mesh.subdivide(
+ number_cuts=ncuts,
+ fractal=roughness * 7 * scale,
+ smoothness=0)
+
+ bpy.ops.mesh.vertices_smooth(repeat=5)
+
+ bpy.ops.object.editmode_toggle()
+
+ if crack_type == 'SPHERE' or crack_type == 'SPHERE_ROUGH':
+ bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=4,
+ size=1,
+ view_align=False,
+ enter_editmode=False,
+ location=(0, 0, 0),
+ rotation=(0, 0, 0),
+ layer=(True, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False,
+ False, False, False, False))
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.faces_shade_smooth()
+ bpy.ops.uv.smart_project(angle_limit=66, island_margin=0)
+
+ bpy.ops.object.editmode_toggle()
+ for v in context.scene.objects.active.data.verts:
+ v.co[0] += 1
+ v.co[0] *= scale
+ v.co[1] *= scale
+ v.co[2] *= scale
+
+ if crack_type == 'SPHERE_ROUGH':
+ for v in context.scene.objects.active.data.verts:
+ v.co[0] += roughness * scale * 0.2 * (random.random() - 0.5)
+ v.co[1] += roughness * scale * 0.1 * (random.random() - 0.5)
+ v.co[2] += roughness * scale * 0.1 * (random.random() - 0.5)
+
+ bpy.context.scene.objects.active.select = True
+
+ '''
+ # Adding fracture material
+ # @todo Doesn't work at all yet.
+ sce = bpy.context.scene
+ if bpy.data.materials.get('fracture')==None:
+ bpy.ops.material.new()
+ bpy.ops.object.material_slot_add()
+ sce.objects.active.material_slots[0].material.name = 'fracture'
+ else:
+ bpy.ops.object.material_slot_add()
+ sce.objects.active.material_slots[0].material
+ = bpy.data.materials['fracture']
+ '''
+
+
+#UNWRAP
+def getsizefrommesh(ob):
+ bb = ob.bound_box
+ return (
+ bb[5][0] - bb[0][0],
+ bb[3][1] - bb[0][1],
+ bb[1][2] - bb[0][2])
+
+
+def getIslands(shard):
+ sm = shard.data
+ islands = []
+ vgroups = []
+ fgroups = []
+
+ vgi = []
+ for v in sm.verts:
+ vgi.append(-1)
+
+ gindex = 0
+ for i in range(len(vgi)):
+ if vgi[i] == -1:
+ gproc = [i]
+ vgroups.append([i])
+ fgroups.append([])
+
+ while len(gproc) > 0:
+ i = gproc.pop(0)
+ for f in sm.faces:
+ #if i in f.verts:
+ for v in f.verts:
+ if v == i:
+ for v1 in f.verts:
+ if vgi[v1] == -1:
+ vgi[v1] = gindex
+ vgroups[gindex].append(v1)
+ gproc.append(v1)
+
+ fgroups[gindex].append(f.index)
+
+ gindex += 1
+
+ #print( gindex)
+
+ if gindex == 1:
+ shards = [shard]
+
+ else:
+ shards = []
+ for gi in range(0, gindex):
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.context.scene.objects.active = shard
+ shard.select = True
+ bpy.ops.object.duplicate(linked=False, mode=1)
+ a = bpy.context.scene.objects.active
+ sm = a.data
+ print (a.name)
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.select_all(action='DESELECT')
+ bpy.ops.object.editmode_toggle()
+
+ for x in range(len(sm.verts) - 1, -1, -1):
+ if vgi[x] != gi:
+ #print('getIslands: selecting')
+ #print('getIslands: ' + str(x))
+ a.data.verts[x].select = True
+
+ print(bpy.context.scene.objects.active.name)
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.delete()
+ bpy.ops.object.editmode_toggle()
+
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+
+ shards.append(a)
+
+ bpy.context.scene.objects.unlink(shard)
+
+ return shards
+
+
+def boolop(ob, cutter, op):
+ sce = bpy.context.scene
+
+ fault = 0
+ new_shards = []
+
+ sizex, sizey, sizez = getsizefrommesh(ob)
+ gsize = sizex + sizey + sizez
+
+ bpy.ops.object.select_all()
+ ob.select = True
+ sce.objects.active = ob
+ cutter.select = False
+
+ bpy.ops.object.modifier_add(type='BOOLEAN')
+ a = sce.objects.active
+ a.modifiers['Boolean'].object = cutter
+ a.modifiers['Boolean'].operation = op
+
+ nmesh = a.create_mesh(sce, apply_modifiers=True, settings='PREVIEW')
+
+ if len(nmesh.verts) > 0:
+ a.modifiers.remove(a.modifiers['Boolean'])
+ bpy.ops.object.duplicate(linked=False, mode=1)
+
+ new_shard = sce.objects.active
+ new_shard.data = nmesh
+ #scene.objects.link(new_shard)
+
+ new_shard.location = a.location
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY')
+
+ sizex, sizey, sizez = getsizefrommesh(new_shard)
+ gsize2 = sizex + sizey + sizez
+
+ if gsize2 > gsize * 1.01: # Size check
+ print (gsize2, gsize, ob.name, cutter.name)
+ fault = 1
+ #print ('boolop: sizeerror')
+
+ elif min(nmesh.edge_face_count) < 2: # Manifold check
+ fault = 1
+
+ if not fault:
+ new_shards = getIslands(new_shard)
+
+ else:
+ sce.objects.unlink(new_shard)
+
+ else:
+ fault = 2
+
+ return fault, new_shards
+
+
+def splitobject(context, ob, crack_type, roughness):
+ scene = context.scene
+
+ size = getsizefrommesh(ob)
+ shards = []
+ scale = max(size) * 1.3
+
+ create_cutter(context, crack_type, scale, roughness)
+ cutter = context.active_object
+ cutter.location = ob.location
+
+ cutter.location[0] += random.random() * size[0] * 0.1
+ cutter.location[1] += random.random() * size[1] * 0.1
+ cutter.location[2] += random.random() * size[2] * 0.1
+ cutter.rotation_euler = [
+ random.random() * 5000.0,
+ random.random() * 5000.0,
+ random.random() * 5000.0]
+
+ scene.objects.active = ob
+ operations = ['INTERSECT', 'DIFFERENCE']
+
+ for op in operations:
+ fault, newshards = boolop(ob, cutter, op)
+
+ shards.extend(newshards)
+ if fault > 0:
+ # Delete all shards in case of fault from previous operation.
+ for s in shards:
+ scene.objects.unlink(s)
+
+ scene.objects.unlink(cutter)
+ #print('splitobject: fault')
+
+ return [ob]
+
+ if shards[0] != ob:
+ bpy.context.scene.objects.unlink(ob)
+
+ bpy.context.scene.objects.unlink(cutter)
+
+ return shards
+
+
+def fracture_basic(context, nshards, crack_type, roughness):
+ tobesplit = []
+ shards = []
+
+ for ob in context.scene.objects:
+ if ob.select:
+ tobesplit.append(ob)
+
+ i = 1 # I counts shards, starts with 1 - the original object
+ iter = 0 # counts iterations, to prevent eternal loops in case
+ # of boolean faults
+
+ maxshards = nshards * len(tobesplit)
+
+ while i < maxshards and len(tobesplit) > 0 and iter < maxshards * 10:
+ ob = tobesplit.pop(0)
+ newshards = splitobject(context, ob, crack_type, roughness)
+
+ tobesplit.extend(newshards)
+
+ if len(newshards) > 1:
+ shards.extend(newshards)
+ #shards.remove(ob)
+
+ i += (len(newshards) - 1)
+
+ #print('fracture_basic: ' + str(i))
+ #print('fracture_basic: lenobs', len(context.scene.objects))
+
+ iter += 1
+
+
+def fracture_group(context, group):
+ tobesplit = []
+ shards = []
+
+ for ob in context.scene.objects:
+ if (ob.select
+ and (len(ob.users_group) == 0 or ob.users_group[0].name != group)):
+ tobesplit.append(ob)
+
+ cutters = bpy.data.groups[group].objects
+
+ # @todo This can be optimized.
+ # Avoid booleans on obs where bbox doesn't intersect.
+ i = 0
+ for ob in tobesplit:
+ for cutter in cutters:
+ fault, newshards = boolop(ob, cutter, 'INTERSECT')
+ shards.extend(newshards)
+
+ if fault == 1:
+ # Delete all shards in case of fault from previous operation.
+ for s in shards:
+ bpy.context.scene.objects.unlink(s)
+
+ #print('fracture_group: fault')
+ #print('fracture_group: ' + str(i))
+
+ return
+
+ i += 1
+
+
+class FractureSimple(bpy.types.Operator):
+ '''Split object with boolean operations for simulation, uses an object.'''
+ bl_idname = "object.fracture_simple"
+ bl_label = "Fracture Object"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ exe = BoolProperty(name="Execute",
+ description="If it shall actually run, for optimal performance...",
+ default=False)
+
+ hierarchy = BoolProperty(name="Generate hierarchy",
+ description="Hierarchy is usefull for simulation of objects" \
+ " breaking in motion.",
+ default=False)
+
+ nshards = IntProperty(name="Number of shards",
+ description="Number of shards the object should be split into.",
+ min=2,
+ default=5)
+
+ crack_type = EnumProperty(name='Crack type',
+ items=(
+ ('FLAT', 'Flat', 'a'),
+ ('FLAT_ROUGH', 'Flat rough', 'a'),
+ ('SPHERE', 'Spherical', 'a'),
+ ('SPHERE_ROUGH', 'Spherical rough', 'a')),
+ description='Look of the fracture surface',
+ default='FLAT')
+
+ roughness = FloatProperty(name="Roughness",
+ description="Roughness of the fracture surface",
+ min=0.0,
+ max=3.0,
+ default=0.5)
+
+ def execute(self, context):
+ #getIslands(context.object)
+ props = self.properties
+
+ if props.exe:
+ fracture_basic(context,
+ props.nshards,
+ props.crack_type,
+ props.roughness)
+
+ return {'FINISHED'}
+
+
+class FractureGroup(bpy.types.Operator):
+ '''Split object with boolean operations for simulation, uses a group.'''
+ bl_idname = "object.fracture_group"
+ bl_label = "Fracture Object (Group)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ exe = BoolProperty(name="Execute",
+ description="If it shall actually run, for optimal performance...",
+ default=False)
+
+ e = []
+ for i, g in enumerate(bpy.data.groups):
+ e.append((g.name, g.name, ''))
+
+ group = EnumProperty(name='Group (hit F8 to refresh list)',
+ items=e,
+ description='Specify the group used for fracturing')
+
+ def execute(self, context):
+ #getIslands(context.object)
+
+ if self.properties.exe:
+ fracture_group(context, self.properties.group)
+
+ return {'FINISHED'}
+
+
+#####################################################################
+# Import Functions
+
+def import_object(obname):
+ opath = "//data.blend\\Object\\" + obname
+ s = os.sep
+ dpath = bpy.utils.script_paths()[0] + \
+ '%saddons%sobject_fracture%sdata.blend\\Object\\' % (s, s, s)
+
+ # DEBUG
+ #print('import_object: ' + opath)
+
+ bpy.ops.wm.link_append(
+ filepath=opath,
+ filename=obname,
+ directory=dpath,
+ filemode=1,
+ link=False,
+ autoselect=True,
+ active_layer=True,
+ instance_groups=True,
+ relative_path=True)
+
+ for ob in bpy.context.selected_objects:
+ ob.location = bpy.context.scene.cursor_location
+
+
+class ImportFractureRecorder(bpy.types.Operator):
+ '''Imports a rigidbody recorder'''
+ bl_idname = "object.import_fracture_recorder"
+ bl_label = "Add Rigidbody Recorder (Fracture)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ import_object("RECORDER")
+
+ return {'FINISHED'}
+
+
+class ImportFractureBomb(bpy.types.Operator):
+ '''Import a bomb'''
+ bl_idname = "object.import_fracture_bomb"
+ bl_label = "Add Bomb (Fracture)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ import_object("BOMB")
+
+ return {'FINISHED'}
+
+
+class ImportFractureProjectile(bpy.types.Operator, ):
+ '''Imports a projectile'''
+ bl_idname = "object.import_fracture_projectile"
+ bl_label = "Add Projectile (Fracture)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ import_object("PROJECTILE")
+
+ return {'FINISHED'}
diff --git a/object_fracture/fracture_setup.py b/object_fracture/fracture_setup.py
new file mode 100644
index 00000000..235dd213
--- /dev/null
+++ b/object_fracture/fracture_setup.py
@@ -0,0 +1,74 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+import bpy
+from bpy.props import *
+
+
+def getsizefrommesh(ob):
+ bb = ob.bound_box
+ return(
+ bb[5][0] - bb[0][0],
+ bb[3][1] - bb[0][1],
+ bb[1][2] - bb[0][2])
+
+
+def setupshards(context):
+ sce = context.scene
+ #print(dir(context))
+ #bpy.data.scenes[0].game_data.all_frames
+
+ tobeprocessed = []
+ for ob in sce.objects:
+ if ob.selected:
+ tobeprocessed.append(ob)
+
+ for ob in tobeprocessed:
+ g = ob.game
+
+ g.physics_type = 'RIGID_BODY'
+ g.use_collision_bounds = 1
+ g.collision_bounds = 'CONVEX_HULL'
+ g.rotation_damping = 0.9
+
+ sizex, sizey, sizez = getsizefrommesh(ob)
+ approxvolume = sizex * sizey * sizez
+ g.mass = approxvolume
+
+ sce.objects.active = ob
+
+ bpy.ops.object.game_property_new()
+ g.properties['prop'].name = 'shard'
+ #sm=FloatProperty(name='shard',description='shardprop',default=0.0)
+ #print (sm)
+ #np=bpy.types.GameFloatProperty(sm)
+ #name='shard',type='BOOL', value=1
+ #print(ob)
+
+
+class SetupFractureShards(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.setup_fracture_shards"
+ bl_label = "Setup Fracture Shards"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ #def poll(self, context):
+
+ def execute(self, context):
+ setupshards(context)
+ return {'FINISHED'}
diff --git a/render_renderfarmfi.py b/render_renderfarmfi.py
new file mode 100644
index 00000000..9c2db3c7
--- /dev/null
+++ b/render_renderfarmfi.py
@@ -0,0 +1,799 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+#
+# Copyright 2009-2010 Laurea University of Applied Sciences
+# Authors: Nathan Letwory, Jesse Kaukonen
+
+import bpy
+import hashlib
+import http.client
+import xmlrpc.client
+import math
+from os.path import abspath, isabs, join, isfile
+
+bpy.CURRENT_VERSION = 2
+bpy.found_newer_version = False
+bpy.up_to_date = False
+bpy.download_location = 'http://www.renderfarm.fi/blender'
+
+bl_addon_info = {
+ 'name': 'Render: Renderfarm.fi',
+ 'author': 'Nathan Letwory <nathan@letworyinteractive.com>, Jesse Kaukonen <jesse.kaukonen@gmail.com>',
+ 'version': str(bpy.CURRENT_VERSION),
+ 'blender': (2, 5, 3),
+ 'location': 'Render > Engine > Renderfarm.fi',
+ 'description': 'Send .blend as session to http://www.renderfarm.fi to render',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Render/Renderfarm.fi',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22927&group_id=153&atid=469',
+ 'category': 'Render'}
+
+bpy.errorMessages = {
+ 'missing_desc': 'You need to enter a title, short and long description',
+ 'missing_creds': 'You haven\'t entered your credentials yet'
+}
+
+bpy.statusMessage = {
+ 'title': 'TRIA_RIGHT',
+ 'shortdesc': 'TRIA_RIGHT',
+ 'longdesc': 'TRIA_RIGHT',
+ 'username': 'TRIA_RIGHT',
+ 'password': 'TRIA_RIGHT'
+}
+
+bpy.errors = []
+bpy.ore_sessions = []
+bpy.queue_selected = -1
+
+def rnaType(rna_type):
+ bpy.types.register(rna_type)
+ return rna_type
+
+def renderEngine(render_engine):
+ bpy.types.register(render_engine)
+ return render_engine
+
+@rnaType
+class ORESession(bpy.types.IDPropertyGroup):
+ pass
+
+@rnaType
+class ORESettings(bpy.types.IDPropertyGroup):
+ pass
+
+# entry point for settings collection
+bpy.types.Scene.PointerProperty(attr='ore_render', type=ORESettings, name='ORE Render', description='ORE Render Settings')
+
+# fill the new struct
+ORESettings.StringProperty(attr='username', name='E-mail', description='E-mail for Renderfarm.fi', maxlen=256, default='')
+ORESettings.StringProperty(attr='password', name='Password', description='Renderfarm.fi password', maxlen=256, default='')
+ORESettings.StringProperty(attr='hash', name='Hash', description='hash calculated out of credentials', maxlen=33, default='')
+
+ORESettings.StringProperty(attr='shortdesc', name='Short description', description='A short description of the scene (100 characters)', maxlen=101, default='')
+ORESettings.StringProperty(attr='longdesc', name='Long description', description='A more elaborate description of the scene (2k)', maxlen=2048, default='')
+ORESettings.StringProperty(attr='title', name='Title', description='Title for this session (128 characters)', maxlen=128, default='')
+ORESettings.StringProperty(attr='url', name='Project URL', description='Project URL. Leave empty if not applicable', maxlen=256, default='')
+
+ORESettings.IntProperty(attr='parts', name='Parts/Frame', description='', min=1, max=1000, soft_min=1, soft_max=64, default=1)
+ORESettings.IntProperty(attr='resox', name='Resolution X', description='X of render', min=1, max=10000, soft_min=1, soft_max=10000, default=1920)
+ORESettings.IntProperty(attr='resoy', name='Resolution Y', description='Y of render', min=1, max=10000, soft_min=1, soft_max=10000, default=1080)
+ORESettings.IntProperty(attr='memusage', name='Memory Usage', description='Estimated maximum memory usage during rendering in MB', min=1, max=6*1024, soft_min=1, soft_max=3*1024, default=256)
+ORESettings.IntProperty(attr='start', name='Start Frame', description='Start Frame', default=1)
+ORESettings.IntProperty(attr='end', name='End Frame', description='End Frame', default=250)
+ORESettings.IntProperty(attr='fps', name='FPS', description='FPS', min=1, max=256, default=25)
+
+ORESettings.BoolProperty(attr='prepared', name='Prepared', description='Set to True if preparation has been run', default=False)
+ORESettings.BoolProperty(attr='debug', name='Debug', description='Verbose output in console', default=False)
+ORESettings.IntProperty(attr='selected_session', name='Selected Session', description='The selected session', default=0)
+
+# session struct
+ORESession.StringProperty(attr='name', name='Name', description='Name of the session', maxlen=128, default='[session]')
+
+licenses = (
+ ('1', 'CC by-nc-nd', 'Creative Commons: Attribution Non-Commercial No Derivatives'),
+ ('2', 'CC by-nc-sa', 'Creative Commons: Attribution Non-Commercial Share Alike'),
+ ('3', 'CC by-nd', 'Creative Commons: Attribution No Derivatives'),
+ ('4', 'CC by-nc', 'Creative Commons: Attribution Non-Commercial'),
+ ('5', 'CC by-sa', 'Creative Commons: Attribution Share Alike'),
+ ('6', 'CC by', 'Creative Commons: Attribution'),
+ ('7', 'Copyright', 'Copyright, no license specified'),
+ )
+ORESettings.EnumProperty(attr='inlicense', items=licenses, name='source license', description='license speficied for the source files', default='1')
+ORESettings.EnumProperty(attr='outlicense', items=licenses, name='output license', description='license speficied for the output files', default='1')
+
+ORESettings.CollectionProperty(attr='sessions', type=ORESession, name='Sessions', description='Sessions on Renderfarm.fi')
+
+# all panels, except render panel
+# Example of wrapping every class 'as is'
+import properties_scene
+for member in dir(properties_scene):
+ subclass = getattr(properties_scene, member)
+ try: subclass.COMPAT_ENGINES.add('RENDERFARMFI_RENDER')
+ except: pass
+del properties_scene
+
+import properties_world
+for member in dir(properties_world):
+ subclass = getattr(properties_world, member)
+ try: subclass.COMPAT_ENGINES.add('RENDERFARMFI_RENDER')
+ except: pass
+del properties_world
+
+import properties_material
+for member in dir(properties_material):
+ subclass = getattr(properties_material, member)
+ try: subclass.COMPAT_ENGINES.add('RENDERFARMFI_RENDER')
+ except: pass
+del properties_material
+
+import properties_object
+for member in dir(properties_object):
+ subclass = getattr(properties_object, member)
+ try: subclass.COMPAT_ENGINES.add('RENDERFARMFI_RENDER')
+ except: pass
+del properties_object
+
+class RenderButtonsPanel(bpy.types.Panel):
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "render"
+ # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
+
+ def poll(self, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in self.COMPAT_ENGINES)
+
+class RENDERFARM_MT_Session(bpy.types.Menu):
+ bl_label = "Show Session"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator('ore.completed_sessions')
+ layout.operator('ore.accept_sessions')
+ layout.operator('ore.active_sessions')
+ layout.separator()
+ layout.operator('ore.cancelled_sessions')
+
+class LOGIN_PT_RenderfarmFi(RenderButtonsPanel):
+ bl_label = 'Login to Renderfarm.fi'
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+ def draw(self, context):
+ layout = self.layout
+ # XXX layout.operator('ore.check_update')
+ ore = context.scene.ore_render
+ updateSessionList(ore)
+ checkStatus(ore)
+
+ if ore.hash=='':
+ col = layout.column()
+ if ore.hash=='':
+ col.prop(ore, 'username', icon=bpy.statusMessage['username'])
+ col.prop(ore, 'password', icon=bpy.statusMessage['password'])
+ layout.operator('ore.login')
+ else:
+ layout.label(text='E-mail and password entered.', icon='INFO')
+ layout.operator('ore.change_user')
+
+class CHECK_PT_RenderfarmFi(RenderButtonsPanel):
+ bl_label = 'Check for updates'
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ def draw(self, context):
+ layout = self.layout
+ ore = context.scene.ore_render
+
+ if bpy.found_newer_version == True:
+ layout.operator('ore.open_download_location')
+ else:
+ if bpy.up_to_date == True:
+ layout.label(text='You have the latest version')
+ layout.operator('ore.check_update')
+
+class SESSIONS_PT_RenderfarmFi(RenderButtonsPanel):
+ bl_label = 'Sessions'
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ def draw(self, context):
+ layout = self.layout
+ ore = context.scene.ore_render
+
+ layout.menu("RENDERFARM_MT_Session")
+ if bpy.queue_selected == 1:
+ layout.label(text='Completed Sessions')
+ elif bpy.queue_selected == 2:
+ layout.label(text='Rendering Sessions')
+ elif bpy.queue_selected == 3:
+ layout.label(text='Pending Sessions')
+ elif bpy.queue_selected == 4:
+ layout.label(text='Cancelled and Rejected Sessions')
+ layout.template_list(ore, 'sessions', ore, 'selected_session', rows=2)
+ if bpy.queue_selected == 3:
+ layout.operator('ore.cancel_session')
+
+class RENDER_PT_RenderfarmFi(RenderButtonsPanel):
+ bl_label = "Scene Settings"
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ def draw(self, context):
+ layout = self.layout
+ sce = context.scene
+ ore = sce.ore_render
+
+ if ore.prepared and ore.hash!='':
+ layout.prop(ore, 'memusage')
+
+ layout.separator()
+ row = layout.row()
+ row.label(text='Resolution: '+str(ore.resox)+'x'+str(ore.resoy))
+
+ layout.separator()
+ row = layout.row()
+ row.prop(ore, 'inlicense')
+ row.prop(ore, 'outlicense')
+
+ layout.separator()
+ row = layout.row()
+ row.operator('ore.upload')
+ row.operator('ore.reset', icon='FILE_REFRESH')
+ else:
+ layout.prop(ore, 'title', icon=bpy.statusMessage['title'])
+ layout.prop(ore, 'shortdesc', icon=bpy.statusMessage['shortdesc'])
+ layout.prop(ore, 'longdesc', icon=bpy.statusMessage['longdesc'])
+ layout.prop(ore, 'url')
+ layout.separator()
+ layout.operator('ore.use_scene_settings', icon='HAND')
+ row = layout.row()
+ row.prop(ore, 'resox')
+ row.prop(ore, 'resoy')
+ layout.separator()
+ layout.prop(ore, 'parts')
+ row = layout.row()
+ row.prop(ore, 'start')
+ row.prop(ore, 'end')
+ layout.prop(ore, 'fps')
+
+ layout.separator()
+ layout.operator('ore.prepare', icon='INFO')
+
+def random_string(length):
+ import string
+ import random
+ return ''.join(random.choice(string.ascii_letters) for ii in range(length + 1))
+
+def encode_multipart_data(data, files):
+ boundary = random_string(30)
+
+ def get_content_type(filename):
+ return 'application/octet-stream' # default this
+
+ def encode_field(field_name):
+ return ('--' + boundary,
+ 'Content-Disposition: form-data; name="%s"' % field_name,
+ '', str(data[field_name]))
+
+ def encode_file(field_name):
+ import codecs
+ filename = files [field_name]
+ return ('--' + boundary,
+ 'Content-Disposition: form-data; name="%s"; filename="%s"' % (field_name, filename),
+ 'Content-Type: %s' % get_content_type(filename),
+ '', str(open(filename, 'rb').read(), encoding='iso-8859-1'))
+
+ lines = []
+ for name in data:
+ lines.extend(encode_field(name))
+ for name in files:
+ lines.extend(encode_file(name))
+ lines.extend(('--%s--' % boundary, ''))
+ body = '\r\n'.join(lines)
+
+ headers = {'content-type': 'multipart/form-data; boundary=' + boundary,
+ 'content-length': str(len(body))}
+
+ return body, headers
+
+def send_post(url, data, files):
+ connection = http.client.HTTPConnection('xmlrpc.renderfarm.fi')
+ connection.request('POST', '/file', *encode_multipart_data(data, files))
+ response = connection.getresponse()
+ res = response.read()
+ return res
+
+def md5_for_file(filepath):
+ md5hash = hashlib.md5()
+ blocksize = 0x10000
+ f = open(filepath, "rb")
+ while True:
+ data = f.read(blocksize)
+ if not data:
+ break
+ md5hash.update(data)
+ return md5hash.hexdigest()
+
+def upload_file(key, userid, sessionid, server, path):
+ assert isabs(path)
+ assert isfile(path)
+
+ data = {
+ 'userId': str(userid),
+ 'sessionKey': key,
+ 'sessionId': sessionid,
+ 'md5sum': md5_for_file(path)
+ }
+ files = {
+ 'blenderfile': path
+ }
+
+ r = send_post(server, data, files)
+
+ #print 'Uploaded %r' % (path)
+
+ return r
+
+def run_upload(key, userid, sessionid, path):
+ #print('Upload', path)
+ r = upload_file(key, userid, sessionid, r'http://xmlrpc.renderfarm.fi/file', path)
+ o = xmlrpc.client.loads(r)
+ #print('Done!')
+
+ return o[0][0]
+
+def ore_upload(op, context):
+ sce = context.scene
+ ore = sce.ore_render
+ if not ore.prepared:
+ op.report(set(['ERROR']), 'Your user or scene information is not complete')
+ return {'CANCELLED'}
+ try:
+ authproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/auth')
+ res = authproxy.auth.getSessionKey(ore.username, ore.hash)
+ key = res['key']
+ userid = res['userId']
+
+ proxy = xmlrpc.client.ServerProxy(r'http://xmlrpc.renderfarm.fi/session')
+ proxy._ServerProxy__transport.user_agent = 'Renderfarm.fi Uploader/%s' % (bpy.CURRENT_VERSION)
+ res = proxy.session.createSession(userid, key)
+ sessionid = res['sessionId']
+ key = res['key']
+ res = run_upload(key, userid, sessionid, bpy.data.filepath)
+ fileid = int(res['fileId'])
+
+ res = proxy.session.setTitle(userid, res['key'], sessionid, ore.title)
+ res = proxy.session.setLongDescription(userid, res['key'], sessionid, ore.longdesc)
+ res = proxy.session.setShortDescription(userid, res['key'], sessionid, ore.shortdesc)
+ if len(ore.url)>0:
+ res = proxy.session.setExternalURLs(userid, res['key'], sessionid, ore.url)
+ res = proxy.session.setStartFrame(userid, res['key'], sessionid, ore.start)
+ res = proxy.session.setEndFrame(userid, res['key'], sessionid, ore.end)
+ res = proxy.session.setSplit(userid, res['key'], sessionid, ore.parts)
+ res = proxy.session.setMemoryLimit(userid, res['key'], sessionid, ore.memusage)
+ res = proxy.session.setXSize(userid, res['key'], sessionid, ore.resox)
+ res = proxy.session.setYSize(userid, res['key'], sessionid, ore.resoy)
+ res = proxy.session.setFrameRate(userid, res['key'], sessionid, ore.fps)
+ res = proxy.session.setOutputLicense(userid, res['key'], sessionid, int(ore.outlicense))
+ res = proxy.session.setInputLicense(userid, res['key'], sessionid, int(ore.inlicense))
+ res = proxy.session.setPrimaryInputFile(userid, res['key'], sessionid, fileid)
+ res = proxy.session.submit(userid, res['key'], sessionid)
+ op.report(set(['INFO']), 'Submission sent to Renderfarm.fi')
+ except xmlrpc.client.Error as v:
+ print('ERROR:', v)
+ op.report(set(['ERROR']), 'An error occurred while sending submission to Renderfarm.fi')
+ except Exception as e:
+ print('Unhandled error:', e)
+ op.report(set(['ERROR']), 'An error occurred while sending submission to Renderfarm.fi')
+
+ return {'FINISHED'}
+
+def setStatus(property, status):
+ if status:
+ bpy.statusMessage[property] = 'ERROR'
+ else:
+ bpy.statusMessage[property] = 'TRIA_RIGHT'
+
+def showStatus(layoutform, property, message):
+ if bpy.statusMessage[property] == 'ERROR':
+ layoutform.label(text='', icon='ERROR')
+
+def checkStatus(ore):
+ bpy.errors = []
+
+ if ore.hash=='' and (ore.username=='' or ore.password==''):
+ bpy.errors.append('missing_creds')
+
+ if '' in (ore.title, ore.longdesc, ore.shortdesc):
+ bpy.errors.append('missing_desc')
+
+ setStatus('username', ore.hash=='' and ore.username=='')
+ setStatus('password', ore.hash=='' and ore.password=='')
+
+ setStatus('title', ore.title=='')
+ setStatus('longdesc', ore.longdesc=='')
+ setStatus('shortdesc', ore.shortdesc=='')
+
+class OreSession:
+
+ def __init__(self, id, title):
+ self.id = id
+ self.title = title
+ self.frames = 0
+ self.startframe = 0
+ self.endframe = 0
+ self.rendertime = 0
+ self.percentage = 0
+
+ def percentageComplete(self):
+ totFrames = self.endframe - self.startframe
+ done = math.floor((self.frames / totFrames)*100)
+ if done > 100: done = 100
+ return done
+
+def xmlSessionsToOreSessions(sessions, queue):
+ bpy.ore_sessions = []
+ completed = sessions[queue]
+ for sid in completed:
+ s = completed[sid]['title']
+ t = completed[sid]['timestamps']
+ sinfo = OreSession(sid, s)
+ if queue in ('completed', 'active'):
+ sinfo.frames = completed[sid]['framesRendered']
+ sinfo.startframe = completed[sid]['startFrame']
+ sinfo.endframe = completed[sid]['endFrame']
+ bpy.ore_sessions.append(sinfo)
+
+def updateSessionList(ore):
+ while(len(ore.sessions) > 0):
+ ore.sessions.remove(0)
+
+ for s in bpy.ore_sessions:
+ ore.sessions.add()
+ session = ore.sessions[-1]
+ session.name = s.title + ' [' + str(s.percentageComplete()) + '% complete]'
+
+class ORE_OpenDownloadLocation(bpy.types.Operator):
+ bl_idname = 'ore.open_download_location'
+ bl_label = 'Download new version for your platform'
+
+ def execute(self, context):
+ import webbrowser
+ webbrowser.open(bpy.download_location)
+ return {'FINISHED'}
+
+class ORE_CancelSession(bpy.types.Operator):
+ bl_idname = 'ore.cancel_session'
+ bl_label = 'Cancel Session'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user')
+ if len(bpy.ore_sessions)>0:
+ s = bpy.ore_sessions[ore.selected_session]
+ try:
+ res = userproxy.user.cancelSession(ore.username, ore.hash, int(s.id))
+ self.report(set(['INFO']), 'Session ' + s.title + ' with id ' + s.id + ' cancelled')
+ except:
+ self.report(set(['ERROR']), 'Could not cancel session ' + s.title + ' with id ' + s.id)
+
+ return {'FINISHED'}
+
+class ORE_GetCompletedSessions(bpy.types.Operator):
+ bl_idname = 'ore.completed_sessions'
+ bl_label = 'Complete'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ bpy.queue_selected = 1
+ userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user')
+
+ sessions = userproxy.user.getAllSessions(ore.username, ore.hash, 'completed')
+
+ xmlSessionsToOreSessions(sessions, 'completed')
+
+ updateSessionList(ore)
+
+ return {'FINISHED'}
+
+class ORE_GetCancelledSessions(bpy.types.Operator):
+ bl_idname = 'ore.cancelled_sessions'
+ bl_label = 'Cancelled'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ bpy.queue_selected = 4
+ userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user')
+
+ sessions = userproxy.user.getAllSessions(ore.username, ore.hash, 'completed')
+
+ xmlSessionsToOreSessions(sessions, 'canceled')
+
+ updateSessionList(ore)
+
+ return {'FINISHED'}
+
+class ORE_GetActiveSessions(bpy.types.Operator):
+ bl_idname = 'ore.active_sessions'
+ bl_label = 'Rendering'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ bpy.queue_selected = 2
+ userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user')
+
+ sessions = userproxy.user.getAllSessions(ore.username, ore.hash, 'active')
+
+ xmlSessionsToOreSessions(sessions, 'active')
+
+ updateSessionList(ore)
+
+ return {'FINISHED'}
+
+class ORE_GetPendingSessions(bpy.types.Operator):
+ bl_idname = 'ore.accept_sessions' # using ORE lingo in API. acceptQueue is session waiting for admin approval
+ bl_label = 'Pending'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ bpy.queue_selected = 3
+ userproxy = xmlrpc.client.ServerProxy(r'https://xmlrpc.renderfarm.fi/user')
+
+ sessions = userproxy.user.getAllSessions(ore.username, ore.hash, 'accept')
+
+ xmlSessionsToOreSessions(sessions, 'accept')
+
+ updateSessionList(ore)
+
+ return {'FINISHED'}
+
+class ORE_CheckUpdate(bpy.types.Operator):
+ bl_idname = 'ore.check_update'
+ bl_label = 'Check for new version'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ blenderproxy = xmlrpc.client.ServerProxy(r'http://xmlrpc.renderfarm.fi/blender')
+ try:
+ self.report(set(['INFO']), 'Checking for newer version on Renderfarm.fi')
+ dl_url = blenderproxy.blender.getCurrentVersion(bpy.CURRENT_VERSION)
+ if len(dl_url['url']) > 0:
+ self.report(set(['INFO']), 'Found a newer version on Renderfarm.fi ' + dl_url['url'])
+ bpy.download_location = dl_url['url']
+ bpy.found_newer_version = True
+ else:
+ bpy.up_to_date = True
+ self.report(set(['INFO']), 'Done checking for newer version on Renderfarm.fi')
+ except xmlrpc.client.Fault as f:
+ print('ERROR:', f)
+ self.report(set(['ERROR']), 'An error occurred while checking for newer version on Renderfarm.fi')
+
+ return {'FINISHED'}
+
+class ORE_LoginOp(bpy.types.Operator):
+ bl_idname = 'ore.login'
+ bl_label = 'Confirm credentials'
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+
+ if ore.hash=='':
+ if ore.password != '' and ore.username != '':
+ ore.hash = hashlib.md5(ore.password.encode() + ore.username.encode()).hexdigest()
+ ore.password = ''
+
+ checkStatus(ore)
+
+ if len(bpy.errors) > 0:
+ ore.prepared = False
+ return {'CANCELLED'}
+
+ return {'FINISHED'}
+
+class ORE_PrepareOp(bpy.types.Operator):
+ '''Checking the scene will also save to the current file when successful!'''
+ bl_idname = 'ore.prepare'
+ bl_label = 'Check scene'
+
+ def execute(self, context):
+ def hasSSSMaterial():
+ for m in bpy.data.materials:
+ if m.subsurface_scattering.enabled:
+ return True
+ return False
+
+ def hasParticleSystem():
+ if len(bpy.data.particles) > 0:
+ self.report({'WARNING'}, "Found particle system")
+ print("Found particle system")
+ return True
+ return False
+
+ def hasSimulation(t):
+ for o in bpy.data.objects:
+ for m in o.modifiers:
+ if isinstance(m, t):
+ self.report({'WARNING'}, "Found simulation: " + str(t))
+ print("Found simulation: " + str(t))
+ return True
+ return False
+
+ def hasFluidSimulation():
+ return hasSimulation(bpy.types.FluidSimulationModifier)
+
+ def hasSmokeSimulation():
+ return hasSimulation(bpy.types.SmokeModifier)
+
+ def hasClothSimulation():
+ return hasSimulation(bpy.types.ClothModifier)
+
+ def hasCollisionSimulation():
+ return hasSimulation(bpy.types.CollisionModifier)
+
+ def hasSoftbodySimulation():
+ return hasSimulation(bpy.types.SoftBodyModifier)
+
+ def hasUnsupportedSimulation():
+ return hasSoftbodySimulation() or hasCollisionSimulation() or hasClothSimulation() or hasSmokeSimulation or hasFluidSimulation() or hasParticleSystem()
+
+ sce = context.scene
+ ore = sce.ore_render
+
+ errors = False
+
+ checkStatus(ore)
+
+ if len(bpy.errors) > 0:
+ ore.prepared = False
+ return {'CANCELLED'}
+
+ rd = sce.render
+ print("=============================================")
+ rd.threads_mode = 'FIXED'
+ rd.threads = 1
+ rd.resolution_x = ore.resox
+ rd.resolution_y = ore.resoy
+ if (rd.resolution_percentage != 100):
+ print("Resolution percentage is not 100. Changing to 100%")
+ self.report({'WARNING'}, "Resolution percentage is not 100. Changing to 100%")
+ errors = True
+ rd.resolution_percentage = 100
+ if rd.file_format != 'PNG':
+ print("Renderfarm.fi always uses PNG for output. Changing to PNG.")
+ self.report({'WARNING'}, "Renderfarm.fi always uses PNG for output. Changing to PNG.")
+ errors = True
+ rd.file_format = 'PNG'
+ if rd.color_mode != 'RGBA':
+ print("Color mode must be set to RGBA. Changing to RGBA.")
+ self.report({'WARNING'}, "Color mode must be set to RGBA. Changing to RGBA.")
+ errors = True
+ rd.color_mode = 'RGBA'
+ if (rd.use_sss == True or hasSSSMaterial())and ore.parts > 1:
+ print("Subsurface Scattering is not supported when rendering with parts > 1. Disabling")
+ self.report({'WARNING'}, "Subsurface Scattering is not supported when rendering with parts > 1. Disabling")
+ errors = True
+ if hasUnsupportedSimulation() == True:
+ print("An unsupported simulation was detected. Please check your settings and remove them")
+ self.report({'WARNING'}, "An unsupported simulation was detected. Please check your settings and remove them")
+ errors = True
+ rd.use_sss = False
+ rd.save_buffers = False
+ rd.free_image_textures = True
+ print("Done checking the scene. Now do a test render")
+ self.report({'INFO'}, "Done checking the scene. Now do a test render")
+ print("=============================================")
+
+ if (errors == True):
+ self.report({'WARNING'}, "Settings were changed or other issues found. Check console and do a test render to make sure everything works.")
+
+ ore.prepared = True
+ rd.engine = 'BLENDER_RENDER'
+ bpy.ops.wm.save_mainfile()
+ rd.engine = 'RENDERFARMFI_RENDER'
+
+ return {'FINISHED'}
+
+class ORE_ResetOp(bpy.types.Operator):
+ bl_idname = "ore.reset"
+ bl_label = "Reset Preparation"
+
+ def execute(self, context):
+ sce = context.scene
+ sce.ore_render.prepared = False
+ return {'FINISHED'}
+
+class ORE_UploaderOp(bpy.types.Operator):
+ bl_idname = "ore.upload"
+ bl_label = "Render on Renderfarm.fi"
+
+ def execute(self, context):
+ rd = context.scene.render
+ rd.engine = 'BLENDER_RENDER'
+ bpy.ops.wm.save_mainfile()
+ return ore_upload(self, context)
+
+class ORE_UseBlenderReso(bpy.types.Operator):
+ bl_idname = "ore.use_scene_settings"
+ bl_label = "Use Scene resolution"
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+
+ ore.resox = sce.render.resolution_x
+ ore.resoy = sce.render.resolution_y
+
+ return {'FINISHED'}
+
+class ORE_ChangeUser(bpy.types.Operator):
+ bl_idname = "ore.change_user"
+ bl_label = "Change user"
+
+ def execute(self, context):
+ ore = context.scene.ore_render
+ ore.password = ''
+ ore.hash = ''
+
+ return {'FINISHED'}
+
+class RenderfarmFi(bpy.types.RenderEngine):
+ bl_idname = 'RENDERFARMFI_RENDER'
+ bl_label = "Renderfarm.fi"
+
+ def render(self, scene):
+ print('Do test renders with Blender Render')
+
+def menu_export(self, context):
+ import os
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".py"
+ self.layout.operator(RenderfarmFi.bl_idname, text=RenderfarmFi.bl_label)
+
+def register():
+ bpy.types.register(RenderfarmFi)
+ bpy.types.register(ORE_OpenDownloadLocation)
+ bpy.types.register(ORE_CancelSession)
+ bpy.types.register(ORE_GetCompletedSessions)
+ bpy.types.register(ORE_GetCancelledSessions)
+ bpy.types.register(ORE_GetActiveSessions)
+ bpy.types.register(ORE_GetPendingSessions)
+ bpy.types.register(ORE_CheckUpdate)
+ bpy.types.register(ORE_LoginOp)
+ bpy.types.register(ORE_PrepareOp)
+ bpy.types.register(ORE_ResetOp)
+ bpy.types.register(ORE_UploaderOp)
+ bpy.types.register(ORE_UseBlenderReso)
+ bpy.types.register(ORE_ChangeUser)
+ bpy.types.register(RENDERFARM_MT_Session)
+ bpy.types.register(LOGIN_PT_RenderfarmFi)
+ bpy.types.register(CHECK_PT_RenderfarmFi)
+ bpy.types.register(SESSIONS_PT_RenderfarmFi)
+ bpy.types.register(RENDER_PT_RenderfarmFi)
+ bpy.types.INFO_MT_render.append(menu_export)
+
+def unregister():
+ bpy.types.unregister(ORESession)
+ bpy.types.unregister(ORESettings)
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_align_tools.py b/space_view3d_align_tools.py
new file mode 100644
index 00000000..95f8843c
--- /dev/null
+++ b/space_view3d_align_tools.py
@@ -0,0 +1,361 @@
+# AlingTools.py (c) 2009, 2010 Gabriel Beaudin (gabhead)
+#
+# ***** 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 *****
+
+"""Align Selected Objects"""
+
+bl_addon_info = {
+ 'name': '3D View: Align Tools',
+ 'author': 'Gabriel Beaudin (gabhead)',
+ 'version': '0.1',
+ 'blender': (2, 5, 3),
+ 'location': 'Tool Shelf',
+ 'description': 'Align selected objects to the active object',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/3D interaction/Align_Tools',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid==22389&group_id=153&atid=468',
+ 'category': '3D View'}
+
+import bpy
+
+##interface
+######################
+class View3DPanel(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+
+class AlignUi(View3DPanel):
+ bl_label = "Align Tools"
+ bl_context = "objectmode"
+
+
+ def draw(self, context):
+ layout = self.layout
+ obj = context.object
+
+ if obj != None:
+ row = layout.row()
+ row.label(text="Active object is: ", icon='OBJECT_DATA')
+ row = layout.row()
+ row.label(obj.name, icon='EDITMODE_HLT')
+
+ box = layout.separator()
+
+ col = layout.column()
+ col.label(text="Align Loc + Rot:", icon='MANIPUL')
+
+
+ col = layout.column(align=False)
+ col.operator("object.AlignObjects",text="XYZ")
+
+ col = layout.column()
+ col.label(text="Align Location:", icon='MAN_TRANS')
+
+ col = layout.column_flow(columns=5,align=True)
+ col.operator("object.AlignObjectsLocationX",text="X")
+ col.operator("object.AlignObjectsLocationY",text="Y")
+ col.operator("object.AlignObjectsLocationZ",text="Z")
+ col.operator("object.AlignObjectsLocationAll",text="All")
+
+ col = layout.column()
+ col.label(text="Align Rotation:", icon='MAN_ROT')
+
+ col = layout.column_flow(columns=5,align=True)
+ col.operator("object.AlignObjectsRotationX",text="X")
+ col.operator("object.AlignObjectsRotationY",text="Y")
+ col.operator("object.AlignObjectsRotationZ",text="Z")
+ col.operator("object.AlignObjectsRotationAll",text="All")
+
+ col = layout.column()
+ col.label(text="Align Scale:", icon='MAN_SCALE')
+
+ col = layout.column_flow(columns=5,align=True)
+ col.operator("object.AlignObjectsScaleX",text="X")
+ col.operator("object.AlignObjectsScaleY",text="Y")
+ col.operator("object.AlignObjectsScaleZ",text="Z")
+ col.operator("object.AlignObjectsScaleAll",text="All")
+
+
+
+
+##Ops
+##################
+
+## Def
+
+##Align all
+def main(context):
+ for i in bpy.context.selected_objects:
+ i.location = bpy.context.active_object.location
+ i.rotation_euler = bpy.context.active_object.rotation_euler
+
+## Align Location
+
+def LocAll(context):
+ for i in bpy.context.selected_objects:
+ i.location = bpy.context.active_object.location
+
+def LocX(context):
+ for i in bpy.context.selected_objects:
+ i.location.x = bpy.context.active_object.location.x
+
+def LocY(context):
+ for i in bpy.context.selected_objects:
+ i.location.y = bpy.context.active_object.location.y
+
+def LocZ(context):
+ for i in bpy.context.selected_objects:
+ i.location.z = bpy.context.active_object.location.z
+
+## Aling Rotation
+def RotAll(context):
+ for i in bpy.context.selected_objects:
+ i.rotation_euler = bpy.context.active_object.rotation_euler
+
+def RotX(context):
+ for i in bpy.context.selected_objects:
+ i.rotation_euler.x = bpy.context.active_object.rotation_euler.x
+
+def RotY(context):
+ for i in bpy.context.selected_objects:
+ i.rotation_euler.y = bpy.context.active_object.rotation_euler.y
+
+def RotZ(context):
+ for i in bpy.context.selected_objects:
+ i.rotation_euler.z = bpy.context.active_object.rotation_euler.z
+## Aling Scale
+def ScaleAll(context):
+ for i in bpy.context.selected_objects:
+ i.scale = bpy.context.active_object.scale
+
+def ScaleX(context):
+ for i in bpy.context.selected_objects:
+ i.scale.x = bpy.context.active_object.scale.x
+
+def ScaleY(context):
+ for i in bpy.context.selected_objects:
+ i.scale.y = bpy.context.active_object.scale.y
+
+def ScaleZ(context):
+ for i in bpy.context.selected_objects:
+ i.scale.z = bpy.context.active_object.scale.z
+
+## Classes
+
+## Align All Rotation And Location
+class AlignOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjects"
+ bl_label = "Align Selected To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ main(context)
+ return {'FINISHED'}
+
+#######################Align Location########################
+## Align LocationAll
+class AlignLocationOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsLocationAll"
+ bl_label = "Align Selected Location To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ LocAll(context)
+ return {'FINISHED'}
+## Align LocationX
+class AlignLocationXOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsLocationX"
+ bl_label = "Align Selected Location X To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ LocX(context)
+ return {'FINISHED'}
+## Align LocationY
+class AlignLocationYOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsLocationY"
+ bl_label = "Align Selected Location Y To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ LocY(context)
+ return {'FINISHED'}
+## Align LocationZ
+class AlignLocationZOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsLocationZ"
+ bl_label = "Align Selected Location Z To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ LocZ(context)
+ return {'FINISHED'}
+
+#######################Align Rotation########################
+## Align RotationAll
+class AlignRotationOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsRotationAll"
+ bl_label = "Align Selected Rotation To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ RotAll(context)
+ return {'FINISHED'}
+## Align RotationX
+class AlignRotationXOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsRotationX"
+ bl_label = "Align Selected Rotation X To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ RotX(context)
+ return {'FINISHED'}
+## Align RotationY
+class AlignRotationYOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsRotationY"
+ bl_label = "Align Selected Rotation Y To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ RotY(context)
+ return {'FINISHED'}
+## Align RotationZ
+class AlignRotationZOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsRotationZ"
+ bl_label = "Align Selected Rotation Z To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ RotZ(context)
+ return {'FINISHED'}
+#######################Align Scale########################
+## Scale All
+class AlignScaleOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsScaleAll"
+ bl_label = "Align Selected Scale To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ ScaleAll(context)
+ return {'FINISHED'}
+## Align ScaleX
+class AlignScaleXOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsScaleX"
+ bl_label = "Align Selected Scale X To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ ScaleX(context)
+ return {'FINISHED'}
+## Align ScaleY
+class AlignScaleYOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsScaleY"
+ bl_label = "Align Selected Scale Y To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ ScaleY(context)
+ return {'FINISHED'}
+## Align ScaleZ
+class AlignScaleZOperator(bpy.types.Operator):
+ ''''''
+ bl_idname = "object.AlignObjectsScaleZ"
+ bl_label = "Align Selected Scale Z To Active"
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ ScaleZ(context)
+ return {'FINISHED'}
+
+## registring
+def register():
+ bpy.types.register(AlignUi)
+ bpy.types.register(AlignOperator)
+ bpy.types.register(AlignLocationOperator)
+ bpy.types.register(AlignLocationXOperator)
+ bpy.types.register(AlignLocationYOperator)
+ bpy.types.register(AlignLocationZOperator)
+ bpy.types.register(AlignRotationOperator)
+ bpy.types.register(AlignRotationXOperator)
+ bpy.types.register(AlignRotationYOperator)
+ bpy.types.register(AlignRotationZOperator)
+ bpy.types.register(AlignScaleOperator)
+ bpy.types.register(AlignScaleXOperator)
+ bpy.types.register(AlignScaleYOperator)
+ bpy.types.register(AlignScaleZOperator)
+
+
+def unregister():
+ bpy.types.unregister(AlignUi)
+ bpy.types.unregister(AlignOperator)
+ bpy.types.unregister(AlignLocationOperator)
+ bpy.types.unregister(AlignLocationXOperator)
+ bpy.types.unregister(AlignLocationYOperator)
+ bpy.types.unregister(AlignLocationZOperator)
+ bpy.types.unregister(AlignRotationOperator)
+ bpy.types.unregister(AlignRotationXOperator)
+ bpy.types.unregister(AlignRotationYOperator)
+ bpy.types.unregister(AlignRotationZOperator)
+ bpy.types.unregister(AlignScaleOperator)
+ bpy.types.unregister(AlignScaleXOperator)
+ bpy.types.unregister(AlignScaleYOperator)
+ bpy.types.unregister(AlignScaleZOperator)
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_materials_utils.py b/space_view3d_materials_utils.py
new file mode 100644
index 00000000..dca94e5c
--- /dev/null
+++ b/space_view3d_materials_utils.py
@@ -0,0 +1,704 @@
+#(c) 2010 Michael Williamson (michaelw)
+#ported from original by Michael Williamsn
+#
+#tested r28370
+#
+#
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+This script has several functions and operators... grouped for convenience
+* assign material:
+ offers the user a list of ALL the materials in the blend file and an additional "new" entry
+ the chosen material will be assigned to all the selected objects in object mode.
+
+ in edit mode the selected faces get the selected material applied.
+
+ if the user chose "new" the new material can be renamed using the "last operator" section of the toolbox
+ After assigning the material "clean material slots" and "material to texface" are auto run to keep things tidy (see description bellow)
+
+
+* select by material
+ in object mode this offers the user a menu of all materials in the blend file
+ any objects using the selected material will become selected, any objects without the material will be removed from selection.
+
+ in edit mode: the menu offers only the materials attached to the current object. It will select the faces that use the material and deselect those that do not.
+
+* clean material slots
+ for all selected objects any empty material slots or material slots with materials that are not used by the mesh faces will be removed.
+
+* Any un-used materials and slots will be removed
+"""
+
+
+bl_addon_info = {
+ 'name': '3D View: Material Utils',
+ 'author': 'michaelw',
+ 'version': '1.3',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Q key',
+ 'description': 'Menu of material tools (assign, select by etc) in the 3D View',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/3D interaction/Materials Utils',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22140&group_id=153&atid=469',
+ 'category': '3D View'}
+
+
+import bpy
+from bpy.props import*
+
+
+def replace_material(m1 , m2, all_objects = False):
+ #replace material named m1 with material named m2
+ #m1 is the name of original material
+ #m2 is the name of the material to replace it with
+ #'all' will replace throughout the blend file
+ try:
+ matorg = bpy.data.materials[m1]
+ matrep = bpy.data.materials[m2]
+
+
+ #store active object
+ scn = bpy.context.scene
+ ob_active = bpy.context.active_object
+
+ if all_objects:
+ objs = bpy.data.objects
+
+ else:
+ objs = bpy.context.selected_editable_objects
+
+ for ob in objs:
+ if ob.type == 'MESH':
+ scn.objects.active = ob
+ print(ob.name)
+ ms = ob.material_slots.values()
+
+ for m in ms:
+ if m.material == matorg:
+ m.material = matrep
+ #don't break the loop as the material can be
+ # ref'd more than once
+
+ #restore active object
+ scn.objects.active = ob_active
+ except:
+ print('no match to replace')
+
+def select_material_by_name(find_mat):
+ #in object mode selects all objects with material find_mat
+ #in edit mode selects all faces with material find_mat
+
+ #check for editmode
+ editmode = False
+
+ scn = bpy.context.scene
+ actob = bpy.context.active_object
+ if actob.mode == 'EDIT':
+ editmode =True
+ bpy.ops.object.mode_set()
+
+
+ if not editmode:
+ objs = bpy.data.objects
+ for ob in objs:
+ if ob.type == 'MESH':
+ ms = ob.material_slots.values()
+ for m in ms:
+ if m.material.name == find_mat:
+ ob.select = True
+ #the active object may not have the mat!
+ #set it to one that does!
+ scn.objects.active = ob
+ break
+ else:
+ ob.select = False
+
+ #deselect non-meshes
+ else:
+ ob.select = False
+
+ else:
+ #it's editmode, so select the faces
+ ob = actob
+ ms = ob.material_slots.values()
+
+ #same material can be on multiple slots
+ slot_indeces =[]
+ i = 0
+ found = False
+ for m in ms:
+ if m.material.name == find_mat:
+ slot_indeces.append(i)
+ found = True
+ me = ob.data
+ for f in me.faces:
+ if f.material_index in slot_indeces:
+ f.select = True
+ else:
+ f.select = False
+ me.update
+ if editmode:
+ bpy.ops.object.mode_set(mode = 'EDIT')
+
+def mat_to_texface():
+ #assigns the first image in each material to the faces in the active uvlayer
+ #for all selected objects
+
+ #check for editmode
+ editmode = False
+
+ actob = bpy.context.active_object
+ if actob.mode == 'EDIT':
+ editmode =True
+ bpy.ops.object.mode_set()
+
+ for ob in bpy.context.selected_editable_objects:
+ #get the materials from slots
+ ms = ob.material_slots.values()
+
+ #build a list of images, one per material
+ images=[]
+ #get the textures from the mats
+ for m in ms:
+ gotimage = False
+ textures = m.material.texture_slots.values()
+ if len(textures) >= 1:
+ for t in textures:
+ if t != None:
+ tex = t.texture
+ if tex.type == 'IMAGE':
+ img = tex.image
+ images.append(img)
+ gotimage =True
+ break
+
+ if not gotimage:
+ print('noimage on', m.name)
+ images.append(None)
+
+ #now we have the images
+ #applythem to the uvlayer
+
+
+ me = ob.data
+ #got uvs?
+ if not me.uv_textures:
+ scn = bpy.context.scene
+ scn.objects.active = ob
+ bpy.ops.mesh.uv_texture_add()
+ scn.objects.active = actob
+
+ #get active uvlayer
+ for t in me.uv_textures:
+ if t.active:
+ uvtex = t.data.values()
+ for f in me.faces:
+ #check that material had an image!
+ if images[f.material_index] != None:
+ uvtex[f.index].image = images[f.material_index]
+ uvtex[f.index].tex =True
+ else:
+ uvtex[f.index].tex =False
+
+ me.update
+
+
+ if editmode:
+ bpy.ops.object.mode_set(mode = 'EDIT')
+
+
+
+def assignmatslots(ob, matlist):
+ #given an object and a list of material names
+ #removes all material slots form the object
+ #adds new ones for each material in matlist
+ #adds the materials to the slots as well.
+
+ scn = bpy.context.scene
+ ob_active = bpy.context.active_object
+ scn.objects.active = ob
+
+ for s in ob.material_slots:
+ bpy.ops.object.material_slot_remove()
+
+
+ #re-add them and assign material
+ i = 0
+ for m in matlist:
+ mat = bpy.data.materials[m]
+ bpy.ops.object.material_slot_add()
+ ob.material_slots.values()[i].material = mat
+ i += 1
+
+ #restore active object:
+ scn.objects.active = ob_active
+
+
+def cleanmatslots():
+ #check for edit mode
+ editmode = False
+ actob = bpy.context.active_object
+ if actob.mode == 'EDIT':
+ editmode =True
+ bpy.ops.object.mode_set()
+
+
+ objs = bpy.context.selected_editable_objects
+
+ for ob in objs:
+ print(ob.name)
+ mats = ob.material_slots.keys()
+
+ #check the faces on the mesh to build a list of used materials
+ usedMatIndex =[] #we'll store used materials indices here
+ faceMats =[]
+ me = ob.data
+ for f in me.faces:
+ #get the material index for this face...
+ faceindex = f.material_index
+
+ #indices will be lost: Store face mat use by name
+ currentfacemat = mats[faceindex]
+ faceMats.append(currentfacemat)
+
+
+ #check if index is already listed as used or not
+ found = 0
+ for m in usedMatIndex:
+ if m == faceindex:
+ found = 1
+ #break
+
+ if found == 0:
+ #add this index to the list
+ usedMatIndex.append(faceindex)
+
+ #re-assign the used mats to the mesh and leave out the unused
+ ml = []
+ mnames = []
+ for u in usedMatIndex:
+ ml.append( mats[u] )
+ #we'll need a list of names to get the face indices...
+ mnames.append(mats[u])
+
+ assignmatslots(ob, ml)
+
+
+ #restore face indices:
+ i = 0
+ for f in me.faces:
+ matindex = mnames.index(faceMats[i])
+ f.material_index = matindex
+ i += 1
+ print('Done')
+ if editmode:
+ bpy.ops.object.mode_set(mode = 'EDIT')
+
+
+
+
+
+def assign_mat(matname="Default"):
+ #get active object so we can restore it later
+ actob = bpy.context.active_object
+
+ #check if material exists, if it doesn't then create it
+ mats =bpy.data.materials
+ found = False
+ for m in mats:
+ if m.name == matname:
+ target = m
+ found = True
+ break
+ if not found:
+ target = bpy.data.materials.new(matname)
+
+
+ #if objectmodeset all faces
+ editmode = False
+ allfaces = True
+ if actob.mode == 'EDIT':
+ editmode =True
+ allfaces = False
+ bpy.ops.object.mode_set()
+
+ objs = bpy.context.selected_editable_objects
+
+ for ob in objs:
+ #set the active object to our object
+ scn = bpy.context.scene
+ scn.objects.active = ob
+
+
+ #check if the material is on the object already
+ if ob.type =='MESH':
+ #check material slots for matname material
+ found=False
+ i = 0
+ mats = ob.material_slots
+ for m in mats:
+ if m.name == matname:
+ found =True
+ index = i
+ #make slot active
+ ob.active_material_index = i
+ break
+ i += 1
+
+ if not found:
+ index=i
+ #the material is not attached to the object
+ #so attach it!
+
+ #add a material slot
+ bpy.ops.object.material_slot_add()
+
+ #make slot active
+ ob.active_material_index = i
+
+ #and assign material to slot
+ ob.material_slots.values()[i].material = target
+ #now assign the material:
+ me =ob.data
+ if allfaces:
+ for f in me.faces:
+ f.material_index = index
+ elif allfaces == False:
+ for f in me.faces:
+ if f.select:
+ f.material_index = index
+ me.update
+
+ #restore the active object
+ bpy.context.scene.objects.active = actob
+ if editmode:
+ bpy.ops.object.mode_set(mode = 'EDIT')
+
+
+
+def check_texture(img,mat):
+ #finds a texture from an image
+ #makes a texture if needed
+ #adds it to the material if it isn't there already
+
+ try:
+ tex = bpy.data.textures[img.name]
+ except:
+ tex = bpy.data.textures.new(name=img.name)
+ finally:
+ tex.type = 'IMAGE'
+ tex = tex.recast_type()
+ tex.image = img
+
+ #see if the material already uses this tex
+ #add it if needed
+ found = False
+ for m in mat.texture_slots:
+ if m and m.texture == tex:
+ found = True
+ break
+ if not found and mat:
+ mat.add_texture(tex, texture_coordinates='UV', map_to='COLOR')
+
+def texface_to_mat():
+ # editmode check here!
+ editmode = False
+ ob = bpy.context.object
+ if ob.mode =='EDIT':
+ editmode = True
+ bpy.ops.object.mode_set()
+
+ for ob in bpy.context.selected_editable_objects:
+
+ faceindex = []
+ unique_images = []
+
+ # get the texface images and store indices
+ if (ob.data.uv_textures):
+ for f in ob.data.active_uv_texture.data:
+ if f.image:
+ img = f.image
+ #build list of unique images
+ if img not in unique_images:
+ unique_images.append(img)
+ faceindex.append(unique_images.index(img))
+
+ else:
+ img = None
+ faceindex.append(None)
+
+
+
+ #check materials for images exist; create if needed
+ matlist = []
+ for i in unique_images:
+ if i:
+ print(i.name)
+ try:
+ m = bpy.data.materials[i.name]
+
+ except:
+ m = bpy.data.materials.new(name = i.name)
+ continue
+
+ finally:
+ matlist.append(m.name)
+ # add textures if needed
+ check_texture(i,m)
+
+ #set up the object material slots
+ assignmatslots(ob, matlist)
+
+ #set texface indices to material slot indices..
+ me = ob.data
+
+ i = 0
+ for f in faceindex:
+ if f != None:
+ me.faces[i].material_index = f
+ i += 1
+ if editmode:
+ bpy.ops.object.mode_set(mode = 'EDIT')
+
+
+#operator classes:
+#---------------------------------------------------------------------
+
+class VIEW3D_OT_texface_to_material(bpy.types.Operator):
+ ''''''
+ bl_idname = "texface_to_material"
+ bl_label = "MW Texface Images to Material/Texture"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ if context.selected_editable_objects:
+ texface_to_mat()
+ return {'FINISHED'}
+ else:
+ self.report({'WARNING'}, "No editable selected objects, could not finish")
+ return {'CANCELLED'}
+
+class VIEW3D_OT_assign_material(bpy.types.Operator):
+ '''assign a material to the selection'''
+ bl_idname = "assign_material"
+ bl_label = "MW Assign Material"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ matname = StringProperty(name = 'Material Name',
+ description = 'Name of Material to Assign',
+ default = "", maxlen = 21)
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ mn = self.properties.matname
+ print(mn)
+ assign_mat(mn)
+ cleanmatslots()
+ mat_to_texface()
+ return {'FINISHED'}
+
+class VIEW3D_OT_clean_material_slots(bpy.types.Operator):
+ '''removes any material slots from the
+ selected objects that are not used by the mesh'''
+ bl_idname = "clean_material_slots"
+ bl_label = "MW Clean Material Slots"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ cleanmatslots()
+ return {'FINISHED'}
+
+class VIEW3D_OT_material_to_texface(bpy.types.Operator):
+ ''''''
+ bl_idname = "material_to_texface"
+ bl_label = "MW Material Images to Texface"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ mat_to_texface()
+ return {'FINISHED'}
+
+class VIEW3D_OT_select_material_by_name(bpy.types.Operator):
+ ''''''
+ bl_idname = "select_material_by_name"
+ bl_label = "MW Select Material By Name"
+ bl_options = {'REGISTER', 'UNDO'}
+ matname = StringProperty(name = 'Material Name',
+ description = 'Name of Material to Select',
+ default = "", maxlen = 21)
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ mn = self.properties.matname
+ select_material_by_name(mn)
+ return {'FINISHED'}
+
+
+class VIEW3D_OT_replace_material(bpy.types.Operator):
+ '''assign a material to the selection'''
+ bl_idname = "replace_material"
+ bl_label = "MW Replace Material"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ matorg = StringProperty(name = 'Material to Replace',
+ description = 'Name of Material to Assign',
+ default = "", maxlen = 21)
+
+ matrep = StringProperty(name = 'Replacement material',
+ description = 'Name of Material to Assign',
+ default = "", maxlen = 21)
+
+ all_objects = BoolProperty(name ='all_objects',
+ description="replace for all objects in this blend file",
+ default = True)
+
+ def poll(self, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ m1 = self.properties.matorg
+ m2 = self.properties.matrep
+ all = self.properties.all_objects
+ replace_material(m1,m2,all)
+ return {'FINISHED'}
+
+#menu classes
+#-------------------------------------------------------------------------------
+class VIEW3D_MT_master_material(bpy.types.Menu):
+ bl_label = "Master Material Menu"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ layout.menu("VIEW3D_MT_assign_material", icon='ZOOMIN')
+ layout.menu("VIEW3D_MT_select_material", icon='HAND')
+ layout.separator()
+ layout.operator("clean_material_slots",
+ text = 'Clean Material Slots', icon='CANCEL')
+ layout.operator("material_to_texface",
+ text = 'Material to Texface',icon='FACESEL_HLT')
+ layout.operator("texface_to_material",
+ text = 'Texface to Material',icon='FACESEL_HLT')
+
+ layout.separator()
+ layout.operator("replace_material",
+ text = 'Replace Material', icon='ARROW_LEFTRIGHT')
+
+
+
+class VIEW3D_MT_assign_material(bpy.types.Menu):
+ bl_label = "Assign Material"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ ob = context
+ layout.label
+ for i in range (len(bpy.data.materials)):
+
+ layout.operator("assign_material",
+ text=bpy.data.materials[i].name,
+ icon='MATERIAL_DATA').matname = bpy.data.materials[i].name
+
+ layout.operator("assign_material",text="Add New",
+ icon='ZOOMIN')
+
+class VIEW3D_MT_select_material(bpy.types.Menu):
+ bl_label = "Select by Material"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ ob = context.object
+ layout.label
+ if ob.mode == 'OBJECT':
+ #show all materials in entire blend file
+ for i in range (len(bpy.data.materials)):
+
+ layout.operator("select_material_by_name",
+ text=bpy.data.materials[i].name,
+ icon='MATERIAL_DATA').matname = bpy.data.materials[i].name
+
+
+ elif ob.mode == 'EDIT':
+ #show only the materials on this object
+ mats = ob.material_slots.keys()
+ for m in mats:
+ layout.operator("select_material_by_name",
+ text=m,
+ icon='MATERIAL_DATA').matname = m
+
+
+
+
+
+
+classes = [
+VIEW3D_OT_assign_material,
+VIEW3D_OT_clean_material_slots,
+VIEW3D_OT_material_to_texface,
+VIEW3D_OT_select_material_by_name,
+VIEW3D_OT_replace_material,
+VIEW3D_OT_texface_to_material,
+VIEW3D_MT_master_material,
+VIEW3D_MT_assign_material,
+VIEW3D_MT_select_material]
+
+def register():
+ register = bpy.types.register
+ for cls in classes:
+ register(cls)
+
+ km = bpy.context.manager.active_keyconfig.keymaps['3D View']
+ kmi = km.items.add('wm.call_menu', 'Q', 'PRESS')
+ kmi.properties.name = "VIEW3D_MT_master_material"
+
+def unregister():
+ unregister = bpy.types.unregister
+ for cls in classes:
+ unregister(cls)
+
+ km = bpy.context.manager.active_keyconfig.keymaps['3D View']
+ for kmi in km.items:
+ if kmi.idname == 'wm.call_menu':
+ if kmi.properties.name == "VIEW3D_MT_master_material":
+ km.remove_item(kmi)
+ break
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_panel_measure.py b/space_view3d_panel_measure.py
new file mode 100644
index 00000000..cbe68b09
--- /dev/null
+++ b/space_view3d_panel_measure.py
@@ -0,0 +1,989 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+"""
+Measure panel
+
+This script displays in OBJECT MODE:
+* The distance of the 3D cursor to the origin of the
+ 3D space (if NOTHING is selected).
+* The distance of the 3D cursor to the center of an object
+ (if exactly ONE object is selected).
+* The distance between 2 object centers
+ (if exactly TWO objects are selected).
+* The surface area of any selected mesh object.
+
+Display in EDIT MODE (Local and Global space supported):
+* The distance of the 3D cursor to the origin
+ (in Local space it is the object center instead).
+* The distance of the 3D cursor to a selected vertex.
+* The distance between 2 selected vertices.
+
+Usage:
+
+This functionality can be accessed via the
+"Properties" panel in 3D View ([N] key).
+
+It's very helpful to use one or two "Empty" objects with
+"Snap during transform" enabled for fast measurement.
+
+Version history:
+v0.7 - Initial support for drawing lines.
+ (Thanks to Algorith for applying my perspective_matrix patch.)
+ The distance value (in BUs) is also drawn in the 3D view now.
+ Also fixed some wrong calculations of global/local distances.
+ Now it's really "what you see is what is calculated".
+ Use bl_addon_info for Add-On information.
+ Use "3D View" in category & name
+ Renamed reenter_editmode to view3d.reenter_editmode.
+ Renamed panel_measure.py into space_view3d_panel_measure.py
+ Active object is only used for edit-mode now. Measurement
+ with exactly one sel. (but not neccessarily active) object
+ now gets the obj via the sel-object array.
+ API change Mathutils -> mathutils (r557)
+ Deselecting 1 of 2 objects now works correctly (active object is ignored).
+ Force a redraw of the area so disabling the "measure_panel_draw"
+ checkbox will clear the line/text.
+ Only calculate area (CPU heavy) if a "area" checkbox is enabled.
+v0.6.4 - Fixed unneeded meshdata duplication (sometimes crashes Blender).
+ The script now correctly calculated the surface area (faceAreaGlobal)
+ of scaled meshes.
+ http://projects.blender.org/tracker/
+ ?func=detail&atid=453&aid=21913&group_id=153
+v0.6.3 - Added register & unregister functions.
+v0.6.2 - Fixed precision of second area property.
+ Reduced display precision to 5 (instead of 6).
+ Added (commented out code) for shortcut [F5] for
+ updating EditMode selection & calculation.
+ Changed the script so it can be managed from the "Add-Ons" tab
+ in the user preferences.
+ Corrected FSF address.
+v0.6.1 - Updated reenter_editmode operator description.
+ Fixed search for selected mesh objects.
+ Added "BU^2" after values that are not yet translated via "unit".
+v0.6
+ *) Fix: Removed EditMode/ObjectMode toggle stuff. This causes all the
+ crashes and is generally not stable.
+ Instead I've added a manual "refresh" button.
+ I registered a new operator OBJECT_OT_reenter_editmode for this.
+ *) Use "unit" settings (i.e. none/metric/imperial)
+ *) Fix: Only display surface area (>=3 objects) if return value is >=0.
+ *) Minor: Renamed objectFaceArea to objectSurfaceArea
+ *) Updated Vector() and tuple() usage.
+ *) Fixed some comments.
+v0.5 - Global surface area (object mode) is now calculated as well.
+ Support area calculation for face selection.
+ Also made measurement panel closed by default. (Area calculation
+ may use up a lot of CPU/RAM in extreme cases)
+v0.4.1 - Various cleanups.
+ Using the shorter "scene" instead of "context.scene"
+ New functions measureGlobal() and measureLocal() for
+ user-friendly access to the "space" setting.
+v0.4 - Calculate & display the surface area of mesh
+ objects (local space only right now).
+ Expanded global/local switch.
+ Made "local" option for 3Dcursor-only in edit mode actually work.
+ Fixed local/global calculation for 3Dcursor<->vertex in edit mode.
+v0.3.2 - Fixed calculation & display of local/global coordinates.
+ The user can now select via dropdown which space is wanted/needed
+ Basically this is a bugfix and new feature at the same time :-)
+v0.3.1 - Fixed bug where "measure_panel_dist" wasn't defined
+ before it was used.
+ Also added the distance calculation "origin -> 3D cursor" for edit mode.
+v0.3 - Support for mesh edit mode (1 or 2 selected vertices)
+v0.2.1 - Small fix (selecting nothing didn't calculate the distance
+ of the cursor from the origin anymore)
+v0.2 - Distance value is now displayed via a FloatProperty widget (and
+ therefore saved to file too right now [according to ideasman42].
+ The value is save inside the scene right now.)
+ Thanks goes to ideasman42 (Campbell Barton) for helping me out on this.
+v0.1 - Initial revision. Seems to work fine for most purposes.
+
+TODO:
+
+There is a random segmentation fault when moving the 3D cursor in edit mode.
+Mainly this happens when clicking inside the white circle of the translation
+manipulator. There may be other cases though.
+
+See the other "todo" comments below.
+
+More links:
+http://gitorious.org/blender-scripts/blender-measure-panel-script
+http://blenderartists.org/forum/showthread.php?t=177800
+
+"""
+
+bl_addon_info = {
+ 'name': '3D View: Measure panel',
+ 'author': 'Buerbaum Martin (Pontiac)',
+ 'version': '0.7',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Properties > Measure',
+ 'description': 'Measure distances between objects',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/3D_interaction/Panel_Measure',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=21445&group_id=153&atid=469',
+ 'category': '3D View'}
+
+
+import bpy
+from bpy.props import *
+from mathutils import Vector, Matrix
+import bgl
+import blf
+
+
+# Precicion for display of float values.
+PRECISION = 4
+
+# Name of the custom properties as stored in the scene.
+COLOR_LOCAL = (1.0, 0.0, 0.0, 0.8)
+COLOR_GLOBAL = (0.0, 0.0, 1.0, 0.8)
+
+
+# Returns a single selected object.
+# Returns None if more than one (or nothing) is selected.
+# Note: Ignores the active object.
+def getSingleObject(context):
+ if len(context.selected_objects) == 1:
+ return context.selected_objects[0]
+
+ return None
+
+
+# Returns a list with 2 3D points (Vector) and a color (RGBA)
+# depending on the current view mode and the selection.
+def getMeasurePoints(context):
+ sce = context.scene
+
+ # Get a single selected object (or nothing).
+ obj = getSingleObject(context)
+
+ if (context.mode == 'EDIT_MESH'):
+ obj = context.active_object
+
+ if (obj and obj.type == 'MESH' and obj.data):
+ # Get mesh data from Object.
+ mesh = obj.data
+
+ # Get transformation matrix from object.
+ ob_mat = obj.matrix_world
+ # Also make an inversed copy! of the matrix.
+ ob_mat_inv = ob_mat.copy().invert()
+
+ # Get the selected vertices.
+ # @todo: Better (more efficient) way to do this?
+ verts_selected = [v for v in mesh.verts if v.select == 1]
+
+ if len(verts_selected) == 0:
+ # Nothing selected.
+ # We measure the distance from...
+ # local ... the object center to the 3D cursor.
+ # global ... the origin to the 3D cursor.
+ cur_loc = sce.cursor_location
+ obj_loc = obj.location.copy()
+
+ # Convert to local space, if needed.
+ if measureLocal(sce):
+ p1 = cur_loc
+ p2 = obj_loc
+ return (p1, p2, COLOR_GLOBAL)
+
+ else:
+ p1 = Vector((0.0, 0.0, 0.0))
+ p2 = cur_loc
+ return (p1, p2, COLOR_GLOBAL)
+
+ elif len(verts_selected) == 1:
+ # One vertex selected.
+ # We measure the distance from the
+ # selected vertex object to the 3D cursor.
+ cur_loc = sce.cursor_location
+ vert_loc = verts_selected[0].co.copy()
+ obj_loc = obj.location.copy()
+
+ # Convert to local or global space.
+ if measureLocal(sce):
+ p1 = obj_loc + vert_loc
+ p2 = cur_loc
+ return (p1, p2, COLOR_LOCAL)
+
+ else:
+ p1 = vert_loc * ob_mat + obj_loc
+ p2 = cur_loc
+ return (p1, p2, COLOR_GLOBAL)
+
+ elif len(verts_selected) == 2:
+ # Two vertices selected.
+ # We measure the distance between the
+ # two selected vertices.
+ obj_loc = obj.location.copy()
+ vert1_loc = verts_selected[0].co.copy()
+ vert2_loc = verts_selected[1].co.copy()
+
+ # Convert to local or global space.
+ if measureLocal(sce):
+ p1 = obj_loc + vert1_loc
+ p2 = obj_loc + vert2_loc
+ return (p1, p2, COLOR_LOCAL)
+
+ else:
+ p1 = obj_loc + vert1_loc * ob_mat
+ p2 = obj_loc + vert2_loc * ob_mat
+ return (p1, p2, COLOR_GLOBAL)
+
+ else:
+ return None
+
+ elif (context.mode == 'OBJECT'):
+ # We are working on object mode.
+
+ if len(context.selected_objects) > 2:
+ return None
+ elif len(context.selected_objects) == 2:
+ # 2 objects selected.
+ # We measure the distance between the 2 selected objects.
+ obj1, obj2 = context.selected_objects
+ obj1_loc = obj1.location.copy()
+ obj2_loc = obj2.location.copy()
+ return (obj1_loc, obj2_loc, COLOR_GLOBAL)
+
+ elif (obj):
+ # One object selected.
+ # We measure the distance from the object to the 3D cursor.
+ cur_loc = sce.cursor_location
+ obj_loc = obj.location.copy()
+ return (obj_loc, cur_loc, COLOR_GLOBAL)
+
+ elif not context.selected_objects:
+ # Nothing selected.
+ # We measure the distance from the origin to the 3D cursor.
+ p1 = Vector((0.0, 0.0, 0.0))
+ p2 = sce.cursor_location
+ return (p1, p2, COLOR_GLOBAL)
+
+ else:
+ return None
+
+
+# Return the area of a face (in global space).
+# @note Copies the functionality of the following functions,
+# but also respects the scaling (via the "obj.matrix_world" parameter):
+# @sa: rna_mesh.c:rna_MeshFace_area_get
+# @sa: math_geom.c:area_quad_v3
+# @sa: math_geom.c:area_tri_v3
+def faceAreaGlobal(face, obj):
+ area = 0.0
+
+ mat = obj.matrix_world
+
+ if len(face.verts) == 4:
+ # Quad
+
+ # Get vertex indices
+ v1, v2, v3, v4 = face.verts
+
+ # Get vertex data
+ v1 = obj.data.verts[v1]
+ v2 = obj.data.verts[v2]
+ v3 = obj.data.verts[v3]
+ v4 = obj.data.verts[v4]
+
+ # Apply transform matrix to vertex coordinates.
+ v1 = v1.co * mat
+ v2 = v2.co * mat
+ v3 = v3.co * mat
+ v4 = v4.co * mat
+
+ vec1 = v2 - v1
+ vec2 = v4 - v1
+
+ n = vec1.cross(vec2)
+
+ area = n.length / 2.0
+
+ vec1 = v4 - v3
+ vec2 = v2 - v3
+
+ n = vec1.cross(vec2)
+
+ area += n.length / 2.0
+
+ elif len(face.verts) == 3:
+ # Triangle
+
+ # Get vertex indices
+ v1, v2, v3 = face.verts
+
+ # Get vertex data
+ v1 = obj.data.verts[v1]
+ v2 = obj.data.verts[v2]
+ v3 = obj.data.verts[v3]
+
+ # Apply transform matrix to vertex coordinates.
+ v1 = v1.co * mat
+ v2 = v2.co * mat
+ v3 = v3.co * mat
+
+ vec1 = v3 - v2
+ vec2 = v1 - v2
+
+ n = vec1.cross(vec2)
+
+ area = n.length / 2.0
+
+ return area
+
+
+# Calculate the surface area of a mesh object.
+# *) Set selectedOnly=1 if you only want to count selected faces.
+# *) Set globalSpace=1 if you want to calculate
+# the global surface area (object mode).
+# Note: Be sure you have updated the mesh data before
+# running this with selectedOnly=1!
+# @todo Support other object types (surfaces, etc...)?
+def objectSurfaceArea(obj, selectedOnly, globalSpace):
+ if (obj and obj.type == 'MESH' and obj.data):
+ areaTotal = 0
+
+ mesh = obj.data
+
+ # Count the area of all the faces.
+ for face in mesh.faces:
+ if not selectedOnly or face.select:
+ if globalSpace:
+ areaTotal += faceAreaGlobal(face, obj)
+ else:
+ areaTotal += face.area
+
+ return areaTotal
+
+ # We can not calculate an area for this object.
+ return -1
+
+
+# User friendly access to the "space" setting.
+def measureGlobal(sce):
+ return (sce.measure_panel_transform == "measure_global")
+
+
+# User friendly access to the "space" setting.
+def measureLocal(sce):
+ return (sce.measure_panel_transform == "measure_local")
+
+
+# Converts 3D coordinates in a 3DRegion
+# into 2D screen coordinates for that region.
+def region3d_get_2d_coordinates(context, loc_3d):
+ # Get screen information
+ mid_x = context.region.width / 2.0
+ mid_y = context.region.height / 2.0
+ width = context.region.width
+ height = context.region.height
+
+ # Get matrices
+ view_mat = context.space_data.region_3d.perspective_matrix
+ total_mat = view_mat
+
+ # order is important
+ vec = total_mat * Vector((loc_3d[0], loc_3d[1], loc_3d[2], 1.0))
+
+ # dehomogenise
+ vec = Vector((
+ vec[0] / vec[3],
+ vec[1] / vec[3],
+ vec[2] / vec[3]))
+
+ x = int(mid_x + vec[0] * width / 2.0)
+ y = int(mid_y + vec[1] * height / 2.0)
+
+ return Vector((x, y, 0))
+
+
+def draw_measurements_callback(self, context):
+ sce = context.scene
+
+ draw = 0
+ if hasattr(sce, "measure_panel_draw"):
+ draw = sce.measure_panel_draw
+
+ # 2D drawing code example
+ #bgl.glBegin(bgl.GL_LINE_STRIP)
+ #bgl.glVertex2i(0, 0)
+ #bgl.glVertex2i(80, 100)
+ #bgl.glEnd()
+
+ # Get measured 3D points and colors.
+ line = getMeasurePoints(context)
+ if (line and draw):
+ p1, p2, color = line
+
+ # Get and convert the Perspective Matrix of the current view/region.
+ view3d = bpy.context.space_data
+ region = view3d.region_3d
+ perspMatrix = region.perspective_matrix
+ tempMat = [perspMatrix[i][j] for i in range(4) for j in range(4)]
+ perspBuff = bgl.Buffer(bgl.GL_FLOAT, 16, tempMat)
+
+ # ---
+ # Store previous OpenGL settings.
+ # Store MatrixMode
+ MatrixMode_prev = bgl.Buffer(bgl.GL_INT, [1])
+ bgl.glGetIntegerv(bgl.GL_MATRIX_MODE, MatrixMode_prev)
+ MatrixMode_prev = MatrixMode_prev[0]
+
+ # Store projection matrix
+ ProjMatrix_prev = bgl.Buffer(bgl.GL_DOUBLE, [16])
+ bgl.glGetFloatv(bgl.GL_PROJECTION_MATRIX, ProjMatrix_prev)
+
+ # Store Line width
+ lineWidth_prev = bgl.Buffer(bgl.GL_FLOAT, [1])
+ bgl.glGetFloatv(bgl.GL_LINE_WIDTH, lineWidth_prev)
+ lineWidth_prev = lineWidth_prev[0]
+
+ # Store GL_BLEND
+ blend_prev = bgl.Buffer(bgl.GL_BYTE, [1])
+ bgl.glGetFloatv(bgl.GL_BLEND, blend_prev)
+ blend_prev = blend_prev[0]
+
+ # Store glColor4f
+ color_prev = bgl.Buffer(bgl.GL_FLOAT, [4])
+ bgl.glGetFloatv(bgl.GL_COLOR, color_prev)
+
+ # ---
+ # Prepare for 3D drawing
+ bgl.glLoadIdentity()
+ bgl.glMatrixMode(bgl.GL_PROJECTION)
+ bgl.glLoadMatrixf(perspBuff)
+
+ bgl.glColor4f(color[0], color[1], color[2], color[3])
+ bgl.glEnable(bgl.GL_BLEND)
+
+ # ---
+ # Draw 3D stuff.
+ width = 2
+ bgl.glLineWidth(width)
+ bgl.glBegin(bgl.GL_LINE_STRIP)
+ bgl.glVertex3f(p1[0], p1[1], p1[2])
+ bgl.glVertex3f(p2[0], p2[1], p2[2])
+ bgl.glEnd()
+
+ # ---
+ # Restore previous OpenGL settings
+ bgl.glLoadIdentity()
+ bgl.glMatrixMode(MatrixMode_prev)
+ bgl.glLoadMatrixf(ProjMatrix_prev)
+ bgl.glLineWidth(lineWidth_prev)
+ if not blend_prev:
+ bgl.glDisable(bgl.GL_BLEND)
+ bgl.glColor4f(color_prev[0],
+ color_prev[1],
+ color_prev[2],
+ color_prev[3])
+
+ # ---
+ # Draw (2D) text
+ # We do this after drawing the lines so
+ # we can draw it OVER the line.
+ coord_2d = region3d_get_2d_coordinates(context, p2 + (p1 - p2) * 0.5)
+ offset = 10 # Offset the text a bit to the right.
+ blf.position(0, coord_2d[0] + offset, coord_2d[1], 0)
+
+ dist = (p1 - p2).length
+ text = "Distance: " + str(round(dist, PRECISION)) + " BU"
+ # @todo Get user pref for text color in 3D View
+ bgl.glColor4f(1.0, 1.0, 1.0, 1.0)
+ blf.size(0, 12, 72) # Prevent font size to randomly change.
+ blf.draw(0, text)
+
+
+class VIEW3D_OT_display_measurements(bpy.types.Operator):
+ '''Display the measurements made in the 'Measure' panel'''
+ bl_idname = "view3d.display_measurements"
+ bl_label = "Display the measurements made in the" \
+ " 'Measure' panel in the 3D View."
+ bl_options = {'REGISTER'}
+
+ def modal(self, context, event):
+ context.area.tag_redraw()
+
+ return {'FINISHED'}
+
+ def execute(self, context):
+ if context.area.type == 'VIEW_3D':
+ if not self.bl_label in context.manager.operators.keys():
+ # Add the region OpenGL drawing callback
+ for WINregion in context.area.regions:
+ if WINregion.type == 'WINDOW':
+ break
+
+ context.manager.add_modal_handler(self)
+ self._handle = WINregion.callback_add(
+ draw_measurements_callback,
+ (self, context),
+ 'POST_PIXEL')
+
+ print("Measure panel display callback added")
+
+ return {'RUNNING_MODAL'}
+
+ else:
+ self.report({'WARNING'}, "View3D not found, cannot run operator")
+ return {'CANCELLED'}
+
+
+class VIEW3D_OT_reenter_editmode(bpy.types.Operator):
+ bl_label = "Re-enter EditMode"
+ bl_idname = "view3d.reenter_editmode"
+ bl_description = "Update mesh data of an active mesh object." \
+ " This is done by exiting and re-entering mesh edit mode."
+ bl_options = {'REGISTER'}
+
+ def invoke(self, context, event):
+
+ # Get the active object.
+ obj = context.active_object
+
+ if (obj and obj.type == 'MESH' and context.mode == 'EDIT_MESH'):
+ # Exit and re-enter mesh EditMode.
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='EDIT')
+ return {'FINISHED'}
+
+ return {'CANCELLED'}
+
+
+class VIEW3D_PT_measure(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_label = "Measure"
+ bl_default_closed = True
+
+ def poll(self, context):
+ # Only display this panel in the object and edit mode 3D view.
+ if (context.area.type == 'VIEW_3D' and
+ (context.mode == 'EDIT_MESH'
+ or context.mode == 'OBJECT')):
+ return 1
+
+ return 0
+
+ def draw_header(self, context):
+ layout = self.layout
+ sce = context.scene
+
+ # Execute operator (this adds the callback)
+ # if it wasn't done yet.
+ bpy.ops.view3d.display_measurements()
+
+ # Define property for the draw setting.
+ sce.BoolProperty(
+ attr="measure_panel_draw",
+ description="Draw distances in 3D View",
+ default=1)
+
+ # Define property for the calc-area setting.
+ # @todo prevent double calculations for each refresh automatically?
+ sce.BoolProperty(
+ attr="measure_panel_calc_area",
+ description="Calculate mesh surface area (heavy CPU" \
+ " usage on bigger meshes)",
+ default=0)
+
+ layout.prop(sce, "measure_panel_draw")
+
+ def draw(self, context):
+ layout = self.layout
+ sce = context.scene
+
+ # Force a redraw.
+ # This prevents the lines still be drawn after
+ # disabling the "measure_panel_draw" checkbox.
+ # @todo Better solution?
+ context.area.tag_redraw()
+
+ # Get a single selected object (or nothing).
+ obj = getSingleObject(context)
+
+ # Define a temporary attribute for the distance value
+ sce.FloatProperty(
+ name="Distance",
+ attr="measure_panel_dist",
+ precision=PRECISION,
+ unit="LENGTH")
+ sce.FloatProperty(
+ attr="measure_panel_area1",
+ precision=PRECISION,
+ unit="AREA")
+ sce.FloatProperty(
+ attr="measure_panel_area2",
+ precision=PRECISION,
+ unit="AREA")
+
+ TRANSFORM = [
+ ("measure_global", "Global",
+ "Calculate values in global space."),
+ ("measure_local", "Local",
+ "Calculate values inside the local object space.")]
+
+ # Define dropdown for the global/local setting
+ bpy.types.Scene.EnumProperty(
+ attr="measure_panel_transform",
+ name="Space",
+ description="Choose in which space you want to measure.",
+ items=TRANSFORM,
+ default='measure_global')
+
+ if (context.mode == 'EDIT_MESH'):
+ obj = context.active_object
+
+ if (obj and obj.type == 'MESH' and obj.data):
+ # "Note: a Mesh will return the selection state of the mesh
+ # when EditMode was last exited. A Python script operating
+ # in EditMode must exit EditMode before getting the current
+ # selection state of the mesh."
+ # http://www.blender.org/documentation/249PythonDoc/
+ # /Mesh.MVert-class.html#sel
+ # We can only provide this by existing & re-entering EditMode.
+ # @todo: Better way to do this?
+
+ # Get mesh data from Object.
+ mesh = obj.data
+
+ # Get transformation matrix from object.
+ ob_mat = obj.matrix_world
+ # Also make an inversed copy! of the matrix.
+ ob_mat_inv = ob_mat.copy()
+ Matrix.invert(ob_mat_inv)
+
+ # Get the selected vertices.
+ # @todo: Better (more efficient) way to do this?
+ verts_selected = [v for v in mesh.verts if v.select == 1]
+
+ if len(verts_selected) == 0:
+ # Nothing selected.
+ # We measure the distance from...
+ # local ... the object center to the 3D cursor.
+ # global ... the origin to the 3D cursor.
+
+ # Get the 2 measure points
+ line = getMeasurePoints(context)
+ if line != 0:
+ dist_vec = line[0] - line[1]
+
+ sce.measure_panel_dist = dist_vec.length
+
+ row = layout.row()
+ row.prop(sce, "measure_panel_dist")
+
+ row = layout.row()
+ row.label(text="", icon='CURSOR')
+ row.label(text="", icon='ARROW_LEFTRIGHT')
+ if measureLocal(sce):
+ row.label(text="Obj. Center")
+ else:
+ row.label(text="Origin [0,0,0]")
+
+ row = layout.row()
+ row.operator("view3d.reenter_editmode",
+ text="Update selection & distance")
+# @todo
+# description="The surface area value can" \
+# " not be updated in mesh edit mode" \
+# " automatically. Press this button" \
+# " to do this manually, after you changed" \
+# " the selection.")
+
+ row = layout.row()
+ row.prop(sce,
+ "measure_panel_transform",
+ expand=True)
+
+ elif len(verts_selected) == 1:
+ # One vertex selected.
+ # We measure the distance from the
+ # selected vertex object to the 3D cursor.
+
+ # Get the 2 measure points
+ line = getMeasurePoints(context)
+ if line != 0:
+ dist_vec = line[0] - line[1]
+
+ sce.measure_panel_dist = dist_vec.length
+
+ row = layout.row()
+ row.prop(sce, "measure_panel_dist")
+
+ row = layout.row()
+ row.label(text="", icon='CURSOR')
+ row.label(text="", icon='ARROW_LEFTRIGHT')
+ row.label(text="", icon='VERTEXSEL')
+
+ row = layout.row()
+ row.operator("view3d.reenter_editmode",
+ text="Update selection & distance")
+
+ row = layout.row()
+ row.prop(sce,
+ "measure_panel_transform",
+ expand=True)
+
+ elif len(verts_selected) == 2:
+ # Two vertices selected.
+ # We measure the distance between the
+ # two selected vertices.
+
+ # Get the 2 measure points
+ line = getMeasurePoints(context)
+ if line != 0:
+ dist_vec = line[0] - line[1]
+
+ sce.measure_panel_dist = dist_vec.length
+
+ row = layout.row()
+ row.prop(sce, "measure_panel_dist")
+
+ row = layout.row()
+ row.label(text="", icon='VERTEXSEL')
+ row.label(text="", icon='ARROW_LEFTRIGHT')
+ row.label(text="", icon='VERTEXSEL')
+
+ row = layout.row()
+ row.operator("view3d.reenter_editmode",
+ text="Update selection & distance")
+
+ row = layout.row()
+ row.prop(sce,
+ "measure_panel_transform",
+ expand=True)
+
+ else:
+ row = layout.row()
+ row.prop(sce, "measure_panel_calc_area",
+ text="Surface area (selected faces):")
+
+ if (sce.measure_panel_calc_area):
+ # Get selected faces
+ # @todo: Better (more efficient) way to do this?
+ faces_selected = [f for f in mesh.faces
+ if f.select == 1]
+
+ if len(faces_selected) > 0:
+ area = objectSurfaceArea(obj, True,
+ measureGlobal(sce))
+ if (area >= 0):
+ row = layout.row()
+ row.label(
+ text=str(len(faces_selected)),
+ icon='FACESEL')
+ sce.measure_panel_area1 = area
+ row.prop(sce, "measure_panel_area1")
+
+ row = layout.row()
+ row.operator("view3d.reenter_editmode",
+ text="Update selection & area")
+
+ row = layout.row()
+ row.prop(sce,
+ "measure_panel_transform",
+ expand=True)
+
+ else:
+ row = layout.row()
+ row.label(text="Selection not supported.",
+ icon='INFO')
+
+ row = layout.row()
+ row.operator("view3d.reenter_editmode",
+ text="Update selection")
+
+ else:
+ row = layout.row()
+ row.operator("view3d.reenter_editmode",
+ text="Update selection")
+
+ elif (context.mode == 'OBJECT'):
+ # We are working on object mode.
+
+ if len(context.selected_objects) > 2:
+ # We have more that 2 objects selected...
+
+ row = layout.row()
+ row.prop(sce, "measure_panel_calc_area",
+ text="Surface area (selected faces):")
+
+ if (sce.measure_panel_calc_area):
+
+ mesh_objects = [o for o in context.selected_objects
+ if (o.type == 'MESH')]
+
+ if (len(mesh_objects) > 0):
+ # ... and at least one of them is a mesh.
+
+ # Calculate and display surface area of the objects.
+ # @todo: Convert to scene units! We do not have a
+ # FloatProperty field here for automatic conversion.
+
+ row = layout.row()
+ for o in mesh_objects:
+ area = objectSurfaceArea(o, False,
+ measureGlobal(sce))
+ if (area >= 0):
+ row = layout.row()
+ row.label(text=o.name, icon='OBJECT_DATA')
+ row.label(text=str(round(area, PRECISION))
+ + " BU^2")
+
+ row = layout.row()
+ row.prop(sce,
+ "measure_panel_transform",
+ expand=True)
+
+ elif len(context.selected_objects) == 2:
+ # 2 objects selected.
+ # We measure the distance between the 2 selected objects.
+
+ obj1, obj2 = context.selected_objects
+
+ # Get the 2 measure points
+ line = getMeasurePoints(context)
+ if line != 0:
+ dist_vec = line[0] - line[1]
+
+ sce.measure_panel_dist = dist_vec.length
+
+ row = layout.row()
+ row.prop(sce, "measure_panel_dist")
+
+ row = layout.row()
+ row.label(text="", icon='OBJECT_DATA')
+ row.prop(obj1, "name", text="")
+
+ row.label(text="", icon='ARROW_LEFTRIGHT')
+
+ row.label(text="", icon='OBJECT_DATA')
+ row.prop(obj2, "name", text="")
+
+ row = layout.row()
+ row.prop(sce, "measure_panel_calc_area",
+ text="Surface area:")
+
+ if (sce.measure_panel_calc_area):
+ # Calculate and display surface area of the objects.
+ area1 = objectSurfaceArea(obj1, False, measureGlobal(sce))
+ area2 = objectSurfaceArea(obj2, False, measureGlobal(sce))
+ if (area1 >= 0 or area2 >= 0):
+ if (area1 >= 0):
+ row = layout.row()
+ row.label(text=obj1.name, icon='OBJECT_DATA')
+ sce.measure_panel_area1 = area1
+ row.prop(sce, "measure_panel_area1")
+
+ if (area2 >= 0):
+ row = layout.row()
+ row.label(text=obj2.name, icon='OBJECT_DATA')
+ sce.measure_panel_area2 = area2
+ row.prop(sce, "measure_panel_area2")
+
+ row = layout.row()
+ row.prop(sce,
+ "measure_panel_transform",
+ expand=True)
+
+ elif (obj):
+ # One object selected.
+ # We measure the distance from the object to the 3D cursor.
+
+ # Get the 2 measure points
+ line = getMeasurePoints(context)
+ if line != 0:
+ dist_vec = line[0] - line[1]
+
+ sce.measure_panel_dist = dist_vec.length
+
+ row = layout.row()
+ #row.label(text=str(dist_vec.length))
+ row.prop(sce, "measure_panel_dist")
+
+ row = layout.row()
+ row.label(text="", icon='CURSOR')
+
+ row.label(text="", icon='ARROW_LEFTRIGHT')
+
+ row.label(text="", icon='OBJECT_DATA')
+ row.prop(obj, "name", text="")
+
+ row = layout.row()
+ row.prop(sce, "measure_panel_calc_area",
+ text="Surface area:")
+
+ if (sce.measure_panel_calc_area):
+ # Calculate and display surface area of the object.
+ area = objectSurfaceArea(obj, False, measureGlobal(sce))
+ if (area >= 0):
+ row = layout.row()
+ row.label(text=obj.name, icon='OBJECT_DATA')
+ sce.measure_panel_area1 = area
+ row.prop(sce, "measure_panel_area1")
+
+ row = layout.row()
+ row.prop(sce,
+ "measure_panel_transform",
+ expand=True)
+
+ elif not context.selected_objects:
+ # Nothing selected.
+ # We measure the distance from the origin to the 3D cursor.
+
+ # Get the 2 measure points
+ line = getMeasurePoints(context)
+ if line != 0:
+ dist_vec = line[0] - line[1]
+
+ sce.measure_panel_dist = dist_vec.length
+
+ row = layout.row()
+ row.prop(sce, "measure_panel_dist")
+
+ row = layout.row()
+ row.label(text="", icon='CURSOR')
+ row.label(text="", icon='ARROW_LEFTRIGHT')
+ row.label(text="Origin [0,0,0]")
+
+ else:
+ row = layout.row()
+ row.label(text="Selection not supported.",
+ icon='INFO')
+
+
+def register():
+ bpy.types.register(VIEW3D_PT_measure)
+ bpy.types.register(VIEW3D_OT_reenter_editmode)
+ bpy.types.register(VIEW3D_OT_display_measurements)
+
+
+def unregister():
+ bpy.types.unregister(VIEW3D_PT_measure)
+ bpy.types.unregister(VIEW3D_OT_reenter_editmode)
+ bpy.types.unregister(VIEW3D_OT_display_measurements)
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_property_chart.py b/space_view3d_property_chart.py
new file mode 100644
index 00000000..bbbc90b3
--- /dev/null
+++ b/space_view3d_property_chart.py
@@ -0,0 +1,232 @@
+#
+# ***** 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 *****
+
+"""List properties of selected objects"""
+
+bl_addon_info = {
+ 'name': '3D View: Object Property Chart',
+ 'author': 'Campbell Barton (ideasman42)',
+ 'version': '0.1',
+ 'blender': (2, 5, 3),
+ 'location': 'Tool Shelf',
+ 'description': 'Edit arbitrary selected properties for objects of the same type',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/3D interaction/Object Property Chart',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?' \
+ 'func=detail&aid=22701&group_id=153&atid=469',
+ 'category': '3D View'}
+
+import bpy
+
+def _property_chart_data_get(self, context):
+ # eg. context.active_object
+ obj = eval("context.%s" % self.context_data_path_active)
+
+ if obj is None:
+ return None, None
+
+ # eg. context.selected_objects[:]
+ selected_objects = eval("context.%s" % self.context_data_path_selected)[:]
+
+ if not selected_objects:
+ return None, None
+
+ return obj, selected_objects
+
+
+def _property_chart_draw(self, context):
+ '''
+ This function can run for different types.
+ '''
+ obj, selected_objects = _property_chart_data_get(self, context)
+
+ if not obj:
+ return
+
+ # active first
+ try:
+ active_index = selected_objects.index(obj)
+ except ValueError:
+ active_index = -1
+
+ if active_index > 0: # not the first alredy
+ selected_objects[0], selected_objects[active_index] = selected_objects[active_index], selected_objects[0]
+
+ id_storage = context.scene
+
+ strings = id_storage.get(self._PROP_STORAGE_ID)
+
+ if strings is None:
+ strings = id_storage[self._PROP_STORAGE_ID] = "data data.name"
+
+ if strings:
+
+ def obj_prop_get(obj, attr_string):
+ """return a pair (rna_base, "rna_property") to give to the rna UI property function"""
+ attrs = attr_string.split(".")
+ val_new = obj
+ for i, attr in enumerate(attrs):
+ val_old = val_new
+ val_new = getattr(val_old, attr, Ellipsis)
+
+ if val_new == Ellipsis:
+ return None, None
+ return val_old, attrs[-1]
+
+ strings = strings.split()
+
+ prop_all = []
+
+ for obj in selected_objects:
+ prop_pairs = []
+ prop_found = False
+ for attr_string in strings:
+ prop_pairs.append(obj_prop_get(obj, attr_string))
+ if prop_found == False and prop_pairs[-1] != (None, None):
+ prop_found = True
+
+ if prop_found:
+ prop_all.append((obj, prop_pairs))
+
+
+ # Collected all props, now display them all
+ layout = self.layout
+
+ row = layout.row(align=True)
+
+ col = row.column()
+ col.label(text="name")
+ for obj, prop_pairs in prop_all:
+ col.prop(obj, "name", text="")
+
+ for i in range(len(strings)):
+ col = row.column()
+
+ # name and copy button
+ rowsub = col.row(align=False)
+ rowsub.label(text=strings[i].rsplit(".", 1)[-1])
+ props = rowsub.operator("wm.chart_copy", text="", icon='PASTEDOWN', emboss=False)
+ props.data_path_active = self.context_data_path_active
+ props.data_path_selected = self.context_data_path_selected
+ props.data_path = strings[i]
+
+ for obj, prop_pairs in prop_all:
+ data, attr = prop_pairs[i]
+ if data:
+ col.prop(data, attr, text="")# , emboss=obj==active_object
+ else:
+ col.label(text="<missing>")
+
+ # edit the display props
+ col = layout.column()
+ col.label(text="Object Properties")
+ col.prop(id_storage, '["%s"]' % self._PROP_STORAGE_ID, text="")
+
+
+class View3DEditProps(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+
+ bl_label = "Property Chart"
+ bl_context = "objectmode"
+
+ _PROP_STORAGE_ID = "view3d_edit_props"
+
+ # _property_chart_draw needs these
+ context_data_path_active = "active_object"
+ context_data_path_selected = "selected_objects"
+
+ draw = _property_chart_draw
+
+
+class SequencerEditProps(bpy.types.Panel):
+ bl_space_type = 'SEQUENCE_EDITOR'
+ bl_region_type = 'UI'
+
+ bl_label = "Property Chart"
+
+ _PROP_STORAGE_ID = "sequencer_edit_props"
+
+ # _property_chart_draw needs these
+ context_data_path_active = "scene.sequence_editor.active_strip"
+ context_data_path_selected = "selected_sequences"
+
+ draw = _property_chart_draw
+
+ def poll(self, context):
+ return context.scene.sequence_editor is not None
+
+# Operator to copy properties
+
+
+def _property_chart_copy(self, context):
+ obj, selected_objects = _property_chart_data_get(self, context)
+
+ if not obj:
+ return
+
+ data_path = self.properties.data_path
+
+ # quick & nasty method!
+ for obj_iter in selected_objects:
+ if obj != obj_iter:
+ try:
+ exec("obj_iter.%s = obj.%s" % (data_path, data_path))
+ except:
+ # just incase we need to know what went wrong!
+ import traceback
+ traceback.print_exc()
+
+from bpy.props import StringProperty
+
+
+class CopyPropertyChart(bpy.types.Operator):
+ "Open a path in a file browser"
+ bl_idname = "wm.chart_copy"
+ bl_label = "Copy properties from active to selected"
+
+ data_path_active = StringProperty()
+ data_path_selected = StringProperty()
+ data_path = StringProperty()
+
+ def execute(self, context):
+ # so attributes are found for '_property_chart_data_get()'
+ self.context_data_path_active = self.properties.data_path_active
+ self.context_data_path_selected = self.properties.data_path_selected
+
+ _property_chart_copy(self, context)
+
+ return {'FINISHED'}
+
+
+def register():
+ bpy.types.register(View3DEditProps)
+ bpy.types.register(SequencerEditProps)
+ bpy.types.register(CopyPropertyChart)
+
+
+def unregister():
+ bpy.types.unregister(View3DEditProps)
+ bpy.types.unregister(SequencerEditProps)
+ bpy.types.unregister(CopyPropertyChart)
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_spacebar_menu.py b/space_view3d_spacebar_menu.py
new file mode 100644
index 00000000..afd8c814
--- /dev/null
+++ b/space_view3d_spacebar_menu.py
@@ -0,0 +1,1501 @@
+#3d_cursor_menu.py (c) 2010 Jonathan Smith (JayDez)
+#Original Script by: Mariano Hidalgo (uselessdreamer)
+#contributed to by: Crouch, sim88, sam, meta-androcto
+#
+#Tested with r28146
+#
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Dynamic Menu
+This adds a the Dynamic Spacebar Menu in the View3D.
+
+Usage:
+* This script gives a basic menu with common simple tools for easy access.
+* Very similar to the Spacebar menu in 2.49
+* Context sensitive for Object. Edit, Sculpt, Pose, Weight/Texture/Vertex Paint.
+* Object sensitive based on object selected in edit mode.
+
+Version history:
+v1.5 - (meta-androcto) - adding context sensitive menus.
+v1.3 - (JayDez) - Changed toggle editmode to an if statement, so that
+ if you are in editmode it will show change to object mode but
+ otherwise it shows change to edit mode. Also added separate icons
+ for change to edit mode and to object mode.
+v1.2 - (JayDez) - Editing docs, changing 3D cursor to dynamic menu,
+ reorganizing menu.
+v1.1 - (meta-androcto) - added editmode menu
+v1.0 - (meta-androcto) - initial final revision (commited to contrib)
+v0.1 through 0.9 - various tests/contributions by various people and scripts
+ Devs: JayDez, Crouch, sim88, meta-androcto, Sam
+ Scripts: 3D Cursor Menu, Original Dynamic Menu
+"""
+
+
+bl_addon_info = {
+ 'name': '3D View: Dynamic Spacebar Menu',
+ 'author': 'JayDez, sim88, meta-androcto', 'sam'
+ 'version': '1.5',
+ 'blender': (2, 5, 3),
+ 'location': 'View3D > Spacebar',
+ 'description': 'Context sensitive spacebar menu',
+ 'warning': '', # used for warning icon and text in addons panel
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/3D_interaction/Dynamic_Spacebar_Menu',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22060&group_id=153&atid=469',
+ 'category': '3D View'}
+
+import bpy
+from bpy import *
+from mathutils import Vector, Matrix
+import math
+
+# Dynamic Menu
+class VIEW3D_MT_Space_Dynamic_Menu(bpy.types.Menu):
+ bl_label = "Dynamic Spacebar Menu"
+
+ def draw(self, context):
+ layout = self.layout
+ settings = context.tool_settings
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ ob = context
+ if ob.mode == 'OBJECT':
+ # Object mode
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+ layout.separator()
+
+ # Add Menu block
+ layout.menu("VIEW3D_MT_AddMenu", icon='OBJECT_DATAMODE')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+ layout.separator()
+
+ # Parent block
+ layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+
+ # Group block
+ layout.menu("VIEW3D_MT_GroupMenu", icon='GROUP')
+ layout.separator()
+
+ # Modifier block
+ layout.operator_menu_enum("object.modifier_add", "type" , icon='MODIFIER')
+ layout.separator()
+
+ # Cursor Block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Align block
+ layout.menu("VIEW3D_MT_AlignMenu", icon='ALIGN')
+ layout.separator()
+
+ # Select block
+ layout.menu("VIEW3D_MT_SelectMenu", icon='RESTRICT_SELECT_OFF')
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Editmode
+ layout.operator("object.editmode_toggle", text="Enter Edit Mode",
+ icon='EDITMODE_HLT')
+
+ # Delete block
+ layout.operator("object.delete", text="Delete Object",
+ icon='CANCEL')
+
+
+ elif ob.mode == 'EDIT_MESH':
+ # Edit mode
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Add block
+ bl_label = "Create"
+ layout.menu("INFO_MT_mesh_add", text="Add Mesh",
+ icon='EDITMODE_HLT')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+ layout.separator()
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_EditCursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Edit block
+ layout.menu("VIEW3D_MT_edit_TK", icon='OUTLINER_OB_MESH')
+ layout.separator()
+
+ # Extrude block
+ layout.menu("VIEW3D_MT_edit_mesh_extrude", icon='EDITMODE_HLT')
+ layout.separator()
+
+ # Tools block
+ layout.menu("VIEW3D_MT_edit_mesh_specials", icon='MODIFIER')
+ layout.menu("VIEW3D_MT_uv_map", icon='MOD_UVPROJECT')
+
+ # Select block
+ layout.menu("VIEW3D_MT_SelectMenu", icon='RESTRICT_SELECT_OFF')
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Object Mode
+ layout.operator("object.editmode_toggle", text="Enter Object Mode",
+ icon='OBJECT_DATAMODE')
+
+ # Delete Block
+ layout.operator("mesh.delete", icon='CANCEL')
+
+ if ob.mode == 'EDIT_CURVE':
+ # Curve menu
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_editing", icon= "PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_editing_falloff", icon= "SMOOTHCURVE")
+ layout.separator()
+
+ # Edit Control Points
+ layout.menu("VIEW3D_MT_EditCurveCtrlpoints",
+ icon='CURVE_BEZCURVE')
+ layout.separator()
+
+ # Edit Curve Specials
+ layout.menu("VIEW3D_MT_EditCurveSpecials",
+ icon='MODIFIER')
+ layout.separator()
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Select Curve Block
+ layout.menu("VIEW3D_MT_SelectCurveMenu", icon='RESTRICT_SELECT_OFF')
+ layout.separator()
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Objectmode
+ layout.operator("object.editmode_toggle", text="Enter Object Mode",
+ icon='OBJECT_DATA')
+
+ # Delete block
+ layout.operator("object.delete", text="Delete Object",
+ icon='CANCEL')
+
+ if ob.mode == 'EDIT_SURFACE':
+ # Surface menu
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_editing", icon= "PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_editing_falloff", icon= "SMOOTHCURVE")
+
+ # Edit Curve Specials
+ layout.menu("VIEW3D_MT_EditCurveSpecials",
+ icon='MODIFIER')
+ layout.separator()
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Select Surface
+ layout.menu("VIEW3D_MT_SelectSurface", icon='RESTRICT_SELECT_OFF')
+ layout.separator()
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Objectmode
+ layout.operator("object.editmode_toggle", text="Enter Object Mode",
+ icon='OBJECT_DATA')
+
+ # Delete block
+ layout.operator("object.delete", text="Delete Object",
+ icon='CANCEL')
+
+ if ob.mode == 'EDIT_METABALL':
+ # Metaball menu
+
+ #Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_editing", icon= "PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_editing_falloff", icon= "SMOOTHCURVE")
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ #Select Metaball
+ layout.menu("VIEW3D_MT_SelectMetaball", icon='RESTRICT_SELECT_OFF')
+ layout.separator()
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Objectmode
+ layout.operator("object.editmode_toggle", text="Enter Object Mode",
+ icon='OBJECT_DATA')
+
+ # Delete block
+ layout.operator("object.delete", text="Delete Object",
+ icon='CANCEL')
+
+ elif ob.mode == 'EDIT_LATTICE':
+ # Lattice menu
+
+ #Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_editing", icon= "PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_editing_falloff", icon= "SMOOTHCURVE")
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ #Select Lattice
+ layout.menu("VIEW3D_MT_select_edit_lattice", icon='RESTRICT_SELECT_OFF')
+ layout.separator()
+
+ layout.operator("lattice.make_regular")
+ layout.separator()
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Objectmode
+ layout.operator("object.editmode_toggle", text="Enter Object Mode",
+ icon='OBJECT_DATA')
+
+ # Delete block
+ layout.operator("object.delete", text="Delete Object",
+ icon='CANCEL')
+
+
+ if context.mode == 'PARTICLE':
+ # Particle menu
+
+ #Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_editing", icon= "PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_editing_falloff", icon= "SMOOTHCURVE")
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Objectmode
+ layout.operator("object.editmode_toggle", text="Enter Object Mode",
+ icon='OBJECT_DATA')
+
+ # Delete block
+ layout.operator("object.delete", text="Delete Object",
+ icon='CANCEL')
+
+ ob = context
+ if ob.mode == 'PAINT_WEIGHT':
+ # Weight paint menu
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Weight Paint block
+ layout.menu("VIEW3D_MT_paint_weight", icon='WPAINT_HLT')
+ layout.separator()
+
+ # History/Cursor Block
+ layout.menu("VIEW3D_MT_undoS", icon='ARROW_LEFTRIGHT')
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Objectmode
+ layout.operator("object.mode_set", text="Enter Object Mode",
+ icon='OBJECT_DATA')
+
+
+
+ elif ob.mode == 'PAINT_VERTEX':
+ # Vertex paint menu
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Vertex Paint block
+ layout.operator("paint.vertex_color_set", icon='VPAINT_HLT')
+ layout.separator()
+
+ # History/Cursor Block
+ layout.menu("VIEW3D_MT_undoS", icon='ARROW_LEFTRIGHT')
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Objectmode
+ layout.operator("object.mode_set", text="Enter Object Mode",
+ icon='OBJECT_DATA')
+
+ elif ob.mode == 'PAINT_TEXTURE':
+ # Texture paint menu
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # History/Cursor Block
+ layout.menu("VIEW3D_MT_undoS", icon='ARROW_LEFTRIGHT')
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Objectmode
+ layout.operator("object.mode_set", text="Enter Object Mode",
+ icon='OBJECT_DATA')
+
+ elif ob.mode == 'SCULPT':
+ # Sculpt menu
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+ layout.separator()
+
+ # Sculpt block
+ layout.menu("VIEW3D_MT_sculpt", icon='SCULPTMODE_HLT')
+ layout.separator()
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # History/Cursor Block
+ layout.menu("VIEW3D_MT_undoS", icon='ARROW_LEFTRIGHT')
+
+ # Toolshelf block
+ layout.operator("view3d.toolshelf", icon='MENU_PANEL')
+ layout.separator()
+
+ # Properties block
+ layout.operator("view3d.properties", icon='MENU_PANEL')
+ layout.separator()
+
+ # Toggle Editmode
+ layout.operator("object.editmode_toggle", text="Enter Edit Mode",
+ icon='EDITMODE_HLT')
+
+ elif ob.mode == 'EDIT_ARMATURE':
+ # Armature menu
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+ layout.separator()
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+ layout.separator()
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Edit Armature roll
+ layout.menu("VIEW3D_MT_edit_armature_roll",
+ icon='BONE_DATA')
+ layout.separator()
+
+ # Edit Armature Toolkit
+ layout.menu("VIEW3D_MT_EditArmatureTK",
+ icon='ARMATURE_DATA')
+ layout.separator()
+
+ # Edit Armature Name
+ layout.menu("VIEW3D_MT_ArmatureName",
+ icon='NEW')
+ layout.separator()
+
+ # Parent block
+ layout.menu("VIEW3D_MT_ParentMenu", icon='ROTACTIVE')
+
+ layout.separator()
+ layout.operator_menu_enum("armature.flags_set", "mode", text="Bone Settings")
+
+ # Edit Armature Select
+ layout.menu("VIEW3D_MT_SelectArmatureMenu",
+ icon='RESTRICT_SELECT_OFF')
+ layout.separator()
+
+ # Edit Armature Specials
+ layout.menu("VIEW3D_MT_armature_specials", icon='MODIFIER')
+ layout.separator()
+
+ # Toggle Posemode
+ layout.operator("object.posemode_toggle", text="Enter Pose Mode",
+ icon='EDITMODE_HLT')
+
+ # Toggle Posemode
+ layout.operator("object.editmode_toggle", text="Enter Object Mode",
+ icon='OBJECT_DATA')
+
+ # Delete block
+ layout.operator("object.delete", text="Delete Object",
+ icon='CANCEL')
+
+
+ if context.mode == 'POSE':
+ # Pose mode menu
+ arm = context.active_object.data
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+
+ # Transform Menu
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # Clear Transform
+ layout.menu("VIEW3D_MT_pose_transform")
+
+ # Cursor Menu
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Select Pose Block
+ layout.menu("VIEW3D_MT_SelectPoseMenu", icon='RESTRICT_SELECT_OFF')
+ layout.separator()
+
+ # Pose Copy Block
+ layout.menu("VIEW3D_MT_PoseCopy", icon='FILE')
+ layout.separator()
+
+
+ if arm.drawtype in ('BBONE', 'ENVELOPE'):
+ layout.operator("transform.transform",
+ text="Scale Envelope Distance").mode = 'BONESIZE'
+
+ layout.menu("VIEW3D_MT_pose_apply")
+ layout.separator()
+
+ layout.operator("pose.relax")
+ layout.separator()
+
+ layout.menu("VIEW3D_MT_KeyframeMenu")
+ layout.separator()
+
+ layout.menu("VIEW3D_MT_pose_pose")
+ layout.menu("VIEW3D_MT_pose_motion")
+ layout.menu("VIEW3D_MT_pose_group")
+ layout.separator()
+
+ layout.menu("VIEW3D_MT_pose_ik")
+ layout.menu("VIEW3D_MT_PoseNames")
+ layout.separator()
+
+ layout.menu("VIEW3D_MT_pose_constraints")
+ layout.separator()
+
+
+
+ layout.operator("pose.quaternions_flip")
+ layout.separator()
+
+ layout.operator_context = 'INVOKE_AREA'
+ layout.operator("pose.armature_layers",
+ text="Change Armature Layers...")
+ layout.operator("pose.bone_layers", text="Change Bone Layers...")
+ layout.separator()
+
+ layout.menu("VIEW3D_MT_pose_showhide")
+ layout.operator_menu_enum("pose.flags_set", 'mode',
+ text="Bone Settings")
+
+ # Toggle Editmode
+ layout.operator("object.editmode_toggle", text="Enter Edit Mode",
+ icon='EDITMODE_HLT')
+
+
+class VIEW3D_MT_AddMenu(bpy.types.Menu):
+ bl_label = "Add Object Menu"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ layout.menu("INFO_MT_mesh_add", text="Add Mesh",
+ icon='OUTLINER_OB_MESH')
+ layout.menu("INFO_MT_curve_add", text="Add Curve",
+ icon='OUTLINER_OB_CURVE')
+ layout.menu("INFO_MT_surface_add", text="Add Surface",
+ icon='OUTLINER_OB_SURFACE')
+ layout.operator_menu_enum("object.metaball_add", "type",
+ icon='OUTLINER_OB_META')
+ layout.operator("object.text_add", text="Add Text",
+ icon='OUTLINER_OB_FONT')
+ layout.separator()
+ layout.menu("INFO_MT_armature_add", text="Add Armature",
+ icon='OUTLINER_OB_ARMATURE')
+ layout.operator("object.add", text="Lattice",
+ icon='OUTLINER_OB_LATTICE').type = 'LATTICE'
+ layout.separator()
+ layout.operator("object.add", text="Add Empty",
+ icon='OUTLINER_OB_EMPTY')
+ layout.separator()
+
+ layout.operator("object.camera_add", text="Camera",
+ icon='OUTLINER_OB_CAMERA')
+ layout.operator_menu_enum("object.lamp_add", "type",
+ icon="OUTLINER_OB_LAMP")
+ layout.separator()
+
+ layout.operator_menu_enum("object.effector_add", "type",
+ text="Force Field",
+ icon='OUTLINER_OB_EMPTY')
+ layout.operator_menu_enum("object.group_instance_add", "group",
+ text="Group Instance",
+ icon='OUTLINER_OB_EMPTY')
+
+
+class VIEW3D_MT_TransformMenu(bpy.types.Menu):
+ bl_label = "Transform Menu"
+
+ # TODO: get rid of the custom text strings?
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("transform.translate", text="Grab/Move")
+ # TODO: sub-menu for grab per axis
+ layout.operator("transform.rotate", text="Rotate")
+ # TODO: sub-menu for rot per axis
+ layout.operator("transform.resize", text="Scale")
+ # TODO: sub-menu for scale per axis
+ layout.separator()
+
+ layout.operator("transform.tosphere", text="To Sphere")
+ layout.operator("transform.shear", text="Shear")
+ layout.operator("transform.warp", text="Warp")
+ layout.operator("transform.push_pull", text="Push/Pull")
+ if context.edit_object and context.edit_object.type == 'ARMATURE':
+ layout.operator("armature.align")
+ else:
+ layout.operator_context = 'EXEC_REGION_WIN'
+ # @todo vvv See alignmenu() in edit.c of b2.4x to get this working.
+ layout.operator("transform.transform",
+ text="Align to Transform Orientation").mode = 'ALIGN'
+ layout.separator()
+
+ layout.operator_context = 'EXEC_AREA'
+
+ 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_MirrorMenu(bpy.types.Menu):
+ bl_label = "Mirror Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("transform.mirror", text="Interactive Mirror")
+ layout.separator()
+
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ props = layout.operator("transform.mirror", text="X Global")
+ props.constraint_axis = (True, False, False)
+ props.constraint_orientation = 'GLOBAL'
+ props = layout.operator("transform.mirror", text="Y Global")
+ props.constraint_axis = (False, True, False)
+ props.constraint_orientation = 'GLOBAL'
+ props = layout.operator("transform.mirror", text="Z Global")
+ props.constraint_axis = (False, False, True)
+ props.constraint_orientation = 'GLOBAL'
+
+ if context.edit_object:
+ layout.separator()
+
+ props = layout.operator("transform.mirror", text="X Local")
+ props.constraint_axis = (True, False, False)
+ props.constraint_orientation = 'LOCAL'
+ props = layout.operator("transform.mirror", text="Y Local")
+ props.constraint_axis = (False, True, False)
+ props.constraint_orientation = 'LOCAL'
+ props = layout.operator("transform.mirror", text="Z Local")
+ props.constraint_axis = (False, False, True)
+ props.constraint_orientation = 'LOCAL'
+
+ layout.operator("object.vertex_group_mirror")
+
+class VIEW3D_MT_ParentMenu(bpy.types.Menu):
+ bl_label = "Parent Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("object.parent_set", text="Set")
+ layout.operator("object.parent_clear", text="Clear")
+
+class VIEW3D_MT_GroupMenu(bpy.types.Menu):
+ bl_label = "Group Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("group.create")
+ layout.operator("group.objects_remove")
+
+ layout.separator()
+
+ layout.operator("group.objects_add_active")
+ layout.operator("group.objects_remove_active")
+
+class VIEW3D_MT_AlignMenu(bpy.types.Menu):
+ bl_label = "Align Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.menu("VIEW3D_MT_view_align_selected")
+
+ layout.separator()
+
+ layout.operator("view3d.view_all", text="Center Cursor and View All").center = True
+ layout.operator("view3d.camera_to_view", text="Align Active Camera to View")
+ layout.operator("view3d.view_selected")
+ layout.operator("view3d.view_center_cursor")
+
+class VIEW3D_MT_SelectMenu(bpy.types.Menu):
+ bl_label = "Select Menu"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ layout.operator("view3d.select_border")
+ layout.operator("view3d.select_circle")
+ layout.separator()
+ 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")
+ layout.operator("object.select_by_layer", text="Select All by Layer")
+ layout.operator_menu_enum("object.select_by_type", "type", text="Select All by Type...")
+ layout.operator("object.select_camera", text="Select Camera")
+ layout.separator()
+ layout.operator_menu_enum("object.select_grouped", "type", text="Grouped")
+ layout.operator_menu_enum("object.select_linked", "type", text="Linked")
+ layout.operator("object.select_pattern", text="Select Pattern...")
+
+class VIEW3D_MT_SelectEditMenu(bpy.types.Menu):
+ bl_label = "Select Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("view3d.select_border")
+ layout.operator("view3d.select_circle")
+
+ layout.separator()
+
+ layout.operator("mesh.select_all", text="Select/Deselect All")
+ layout.operator("mesh.select_inverse", text="Inverse")
+
+ layout.separator()
+
+ layout.operator("mesh.select_random", text="Random")
+ layout.operator("mesh.select_nth", text="Every N Number of Verts")
+ layout.operator("mesh.edges_select_sharp", text="Sharp Edges")
+ layout.operator("mesh.faces_select_linked_flat", text="Linked Flat Faces")
+ layout.operator("mesh.faces_select_interior", text="Interior Faces")
+ layout.operator("mesh.select_axis", text="Side of Active")
+
+ layout.separator()
+
+ layout.operator("mesh.select_by_number_vertices", text="Triangles").type = 'TRIANGLES'
+ layout.operator("mesh.select_by_number_vertices", text="Quads").type = 'QUADS'
+ if context.scene.tool_settings.mesh_selection_mode[2] == False:
+ layout.operator("mesh.select_non_manifold", text="Non Manifold")
+ layout.operator("mesh.select_by_number_vertices", text="Loose Verts/Edges").type = 'OTHER'
+ layout.operator("mesh.select_similar", text="Similar")
+
+ layout.separator()
+
+ layout.operator("mesh.select_less", text="Less")
+ layout.operator("mesh.select_more", text="More")
+
+ layout.separator()
+
+ layout.operator("mesh.select_mirror", text="Mirror")
+
+ layout.operator("mesh.select_linked", text="Linked")
+ layout.operator("mesh.select_vertex_path", text="Vertex Path")
+ layout.operator("mesh.loop_multi_select", text="Edge Loop")
+ layout.operator("mesh.loop_multi_select", text="Edge Ring").ring = True
+
+ layout.separator()
+
+ layout.operator("mesh.loop_to_region")
+ layout.operator("mesh.region_to_loop")
+
+class VIEW3D_MT_SelectCurveMenu(bpy.types.Menu):
+ bl_label = "Select Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("view3d.select_border")
+ layout.operator("view3d.select_circle")
+
+ layout.separator()
+
+ 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")
+
+ layout.separator()
+
+ layout.operator("curve.de_select_first")
+ layout.operator("curve.de_select_last")
+ layout.operator("curve.select_next")
+ layout.operator("curve.select_previous")
+
+ layout.separator()
+
+ layout.operator("curve.select_more")
+ layout.operator("curve.select_less")
+
+class VIEW3D_MT_SelectArmatureMenu(bpy.types.Menu):
+ bl_label = "Select Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("view3d.select_border")
+
+
+ layout.separator()
+
+ layout.operator("armature.select_all", text="Select/Deselect All")
+ layout.operator("armature.select_inverse", text="Inverse")
+
+ layout.separator()
+
+ layout.operator("armature.select_hierarchy", text="Parent").direction = 'PARENT'
+ layout.operator("armature.select_hierarchy", text="Child").direction = 'CHILD'
+
+ layout.separator()
+
+ props = layout.operator("armature.select_hierarchy", text="Extend Parent")
+ props.extend = True
+ props.direction = 'PARENT'
+
+ props = layout.operator("armature.select_hierarchy", text="Extend Child")
+ props.extend = True
+ props.direction = 'CHILD'
+
+ layout.operator("object.select_pattern", text="Select Pattern...")
+
+
+class VIEW3D_MT_SelectPoseMenu(bpy.types.Menu):
+ bl_label = "Select Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("view3d.select_border")
+
+ layout.separator()
+
+ 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", text="Parent").direction = 'PARENT'
+ layout.operator("pose.select_hierarchy", text="Child").direction = 'CHILD'
+
+ layout.separator()
+
+ props = layout.operator("pose.select_hierarchy", text="Extend Parent")
+ props.extend = True
+ props.direction = 'PARENT'
+
+ props = layout.operator("pose.select_hierarchy", text="Extend Child")
+ props.extend = True
+ props.direction = 'CHILD'
+
+ layout.separator()
+
+ layout.operator_menu_enum("pose.select_grouped", "type", text="Grouped")
+ layout.operator("object.select_pattern", text="Select Pattern...")
+
+class VIEW3D_MT_PoseCopy(bpy.types.Menu):
+ bl_label = "Pose Copy"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("pose.copy")
+ layout.operator("pose.paste")
+ layout.operator("pose.paste",
+ text="Paste X-Flipped Pose").flipped = True
+ layout.separator()
+
+class VIEW3D_MT_PoseNames(bpy.types.Menu):
+ bl_label = "Pose Copy"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator_context = 'EXEC_AREA'
+ layout.operator("pose.autoside_names",
+ text="AutoName Left/Right").axis = 'XAXIS'
+ layout.operator("pose.autoside_names",
+ text="AutoName Front/Back").axis = 'YAXIS'
+ layout.operator("pose.autoside_names",
+ text="AutoName Top/Bottom").axis = 'ZAXIS'
+
+ layout.operator("pose.flip_names")
+
+
+class VIEW3D_MT_SelectSurface(bpy.types.Menu):
+ bl_label = "Select Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("view3d.select_border")
+ layout.operator("view3d.select_circle")
+
+ layout.separator()
+
+ 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")
+
+ layout.separator()
+
+ layout.operator("curve.select_row")
+
+ layout.separator()
+
+ layout.operator("curve.select_more")
+ layout.operator("curve.select_less")
+
+class VIEW3D_MT_SelectMetaball(bpy.types.Menu):
+ bl_label = "Select Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("view3d.select_border")
+
+ layout.separator()
+
+ #layout.operator("mball.select_deselect_all_metaelems")
+ layout.operator("mball.select_inverse_metaelems")
+
+ layout.separator()
+
+ layout.operator("mball.select_random_metaelems")
+
+class VIEW3D_MT_edit_TK(bpy.types.Menu):
+ bl_label = "Edit Mesh"
+
+ def draw(self, context):
+ layout = self.layout
+ row = layout.row()
+
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ prop = layout.operator("wm.context_set_value",
+ text="Select By Vertex", icon='VERTEXSEL')
+ prop.value = "(True, False, False)"
+ prop.data_path = "tool_settings.mesh_selection_mode"
+ layout.menu("VIEW3D_MT_edit_mesh_vertices", icon='VERTEXSEL')
+
+ prop = layout.operator("wm.context_set_value",
+ text="Select By Edge", icon='EDGESEL')
+ prop.value = "(False, True, False)"
+ prop.data_path = "tool_settings.mesh_selection_mode"
+ layout.menu("VIEW3D_MT_edit_mesh_edges", icon='EDGESEL')
+
+ prop = layout.operator("wm.context_set_value",
+ text="Select By Face", icon='FACESEL')
+ prop.value = "(False, False, True)"
+ prop.data_path = "tool_settings.mesh_selection_mode"
+ layout.menu("VIEW3D_MT_edit_mesh_faces", icon='FACESEL')
+ layout.separator()
+
+ layout.menu("VIEW3D_MT_edit_mesh_normals", icon='META_DATA')
+ layout.operator("mesh.loopcut_slide",
+ text="Loopcut", icon='EDIT_VEC')
+
+class VIEW3D_MT_editM_Edge(bpy.types.Menu):
+ bl_label = "Edges"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ layout.operator("mesh.mark_seam")
+ layout.operator("mesh.mark_seam", text="Clear Seam").clear = True
+ layout.separator()
+
+ layout.operator("mesh.mark_sharp")
+ layout.operator("mesh.mark_sharp", text="Clear Sharp").clear = True
+ layout.operator("mesh.extrude_move_along_normals", text="Extrude")
+ layout.separator()
+
+ layout.operator("mesh.edge_rotate",
+ text="Rotate Edge CW").direction = 'CW'
+ layout.operator("mesh.edge_rotate",
+ text="Rotate Edge CCW").direction = 'CCW'
+ layout.separator()
+
+ layout.operator("TFM_OT_edge_slide", text="Edge Slide")
+ layout.operator("mesh.loop_multi_select", text="Edge Loop")
+ layout.operator("mesh.loop_multi_select", text="Edge Ring").ring = True
+ layout.operator("mesh.loop_to_region")
+ layout.operator("mesh.region_to_loop")
+
+
+class VIEW3D_MT_EditCurveCtrlpoints(bpy.types.Menu):
+ bl_label = "Control Points"
+
+ def draw(self, context):
+ layout = self.layout
+
+ edit_object = context.edit_object
+
+ if edit_object.type == 'CURVE':
+ layout.operator("transform.transform").mode = 'TILT'
+ layout.operator("curve.tilt_clear")
+ layout.operator("curve.separate")
+
+ layout.separator()
+
+ layout.operator_menu_enum("curve.handle_type_set", "type")
+
+ layout.separator()
+
+ layout.menu("VIEW3D_MT_hook")
+
+
+class VIEW3D_MT_EditCurveSegments(bpy.types.Menu):
+ bl_label = "Curve Segments"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("curve.subdivide")
+ layout.operator("curve.switch_direction")
+
+class VIEW3D_MT_EditCurveSpecials(bpy.types.Menu):
+ bl_label = "Specials"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator("curve.subdivide")
+ layout.operator("curve.switch_direction")
+ layout.operator("curve.spline_weight_set")
+ layout.operator("curve.radius_set")
+ layout.operator("curve.smooth")
+ layout.operator("curve.smooth_radius")
+
+class VIEW3D_MT_EditArmatureTK(bpy.types.Menu):
+ bl_label = "Armature Tools"
+
+ def draw(self, context):
+ layout = self.layout
+
+ # Edit Armature
+
+ layout.operator("transform.transform", text="Scale Envelope Distance").mode = 'BONESIZE'
+
+ layout.operator("transform.transform", text="Scale B-Bone Width").mode = 'BONESIZE'
+
+ layout.separator()
+
+ layout.operator("armature.extrude_move")
+
+ layout.operator("armature.extrude_forked")
+
+ layout.operator("armature.duplicate_move")
+ layout.operator("armature.merge")
+ layout.operator("armature.fill")
+ layout.operator("armature.delete")
+ layout.operator("armature.separate")
+
+ layout.separator()
+
+ layout.operator("armature.subdivide_multi", text="Subdivide")
+ layout.operator("armature.switch_direction", text="Switch Direction")
+
+class VIEW3D_MT_ArmatureName(bpy.types.Menu):
+ bl_label = "Armature Name"
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.operator_context = 'EXEC_AREA'
+ layout.operator("armature.autoside_names", text="AutoName Left/Right").type = 'XAXIS'
+ layout.operator("armature.autoside_names", text="AutoName Front/Back").type = 'YAXIS'
+ layout.operator("armature.autoside_names", text="AutoName Top/Bottom").type = 'ZAXIS'
+ layout.operator("armature.flip_names")
+ layout.separator()
+
+class VIEW3D_MT_KeyframeMenu(bpy.types.Menu):
+ bl_label = "Keyframe Menu"
+
+ def draw(self, context):
+ layout = self.layout
+
+ # Keyframe Bleck
+ layout.operator("anim.keyframe_insert_menu",
+ text="Insert Keyframe...")
+ layout.operator("anim.keyframe_delete_v3d",
+ text="Delete Keyframe...")
+ layout.operator("anim.keying_set_active_set",
+ text="Change Keying Set...")
+ layout.separator()
+
+# Classes for VIEW3D_MT_CursorMenu()
+class VIEW3D_OT_pivot_cursor(bpy.types.Operator):
+ "Cursor as Pivot Point"
+ bl_idname = "view3d.pivot_cursor"
+ bl_label = "Cursor as Pivot Point"
+
+ def poll(self, context):
+ return bpy.context.space_data.pivot_point != 'CURSOR'
+
+ def execute(self, context):
+ bpy.context.space_data.pivot_point = 'CURSOR'
+ return {'FINISHED'}
+
+class VIEW3D_OT_revert_pivot(bpy.types.Operator):
+ "Revert Pivot Point"
+ bl_idname = "view3d.revert_pivot"
+ bl_label = "Reverts Pivot Point to median"
+
+ def poll(self, context):
+ return bpy.context.space_data.pivot_point != 'MEDIAN_POINT'
+
+ def execute(self, context):
+ bpy.context.space_data.pivot_point = 'MEDIAN_POINT'
+ # @todo Change this to 'BOUDNING_BOX_CENTER' if needed...
+ return{'FINISHED'}
+
+class VIEW3D_MT_CursorMenu(bpy.types.Menu):
+ bl_label = "Snap Cursor Menu"
+
+ def draw(self, context):
+
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("view3d.snap_cursor_to_selected",
+ text="Cursor to Selected")
+ layout.operator("view3d.snap_cursor_to_center",
+ text="Cursor to Center")
+ layout.operator("view3d.snap_cursor_to_grid",
+ text="Cursor to Grid")
+ layout.operator("view3d.snap_cursor_to_active",
+ text="Cursor to Active")
+ layout.separator()
+ layout.operator("view3d.snap_selected_to_cursor",
+ text="Selection to Cursor")
+ layout.operator("view3d.snap_selected_to_grid",
+ text="Selection to Grid")
+ layout.separator()
+ layout.operator("view3d.pivot_cursor",
+ text="Set Cursor as Pivot Point")
+ layout.operator("view3d.revert_pivot",
+ text="Revert Pivot Point")
+
+class VIEW3D_MT_EditCursorMenu(bpy.types.Menu):
+ bl_label = "Snap Cursor Menu"
+
+ def draw(self, context):
+
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("view3d.snap_cursor_to_selected",
+ text="Cursor to Selected")
+ layout.operator("view3d.snap_cursor_to_center",
+ text="Cursor to Center")
+ layout.operator("view3d.snap_cursor_to_grid",
+ text="Cursor to Grid")
+ layout.operator("view3d.snap_cursor_to_active",
+ text="Cursor to Active")
+ layout.separator()
+ layout.operator("view3d.snap_selected_to_cursor",
+ text="Selection to Cursor")
+ layout.operator("view3d.snap_selected_to_grid",
+ text="Selection to Grid")
+ layout.separator()
+ layout.operator("view3d.pivot_cursor",
+ text="Set Cursor as Pivot Point")
+ layout.operator("view3d.revert_pivot",
+ text="Revert Pivot Point")
+ layout.operator("view3d.snap_cursor_to_edge_intersection",
+ text="Cursor to Edge Intersection")
+ layout.operator("transform.snap_type", text="Snap Tools",
+ icon='SNAP_ON')
+
+def abs(val):
+ if val > 0:
+ return val
+ return -val
+
+def LineLineIntersect (p1, p2, p3, p4):
+ # based on Paul Bourke's Shortest Line Between 2 lines
+
+ min = 0.0000001
+
+ v1 = Vector((p1.x - p3.x, p1.y - p3.y, p1.z - p3.z))
+ v2 = Vector((p4.x - p3.x, p4.y - p3.y, p4.z - p3.z))
+
+ if abs(v2.x) < min and abs(v2.y) < min and abs(v2.z) < min:
+ return None
+
+ v3 = Vector((p2.x - p1.x, p2.y - p1.y, p2.z - p1.z))
+
+ if abs(v3.x) < min and abs(v3.y) < min and abs(v3.z) < min:
+ return None
+
+ d1 = v1.dot(v2)
+ d2 = v2.dot(v3)
+ d3 = v1.dot(v3)
+ d4 = v2.dot(v2)
+ d5 = v3.dot(v3)
+
+ d = d5 * d4 - d2 * d2
+
+ if abs(d) < min:
+ return None
+
+ n = d1 * d2 - d3 * d4
+
+ mua = n / d
+ mub = (d1 + d2 * (mua)) / d4
+
+ return [Vector((p1.x + mua * v3.x, p1.y + mua * v3.y, p1.z + mua * v3.z)),
+ Vector((p3.x + mub * v2.x, p3.y + mub * v2.y, p3.z + mub * v2.z))]
+
+def edgeIntersect(context, operator):
+
+ obj = context.active_object
+
+ if (obj.type != "MESH"):
+ operator.report({'ERROR'}, "Object must be a mesh")
+ return None
+
+ edges = [];
+ mesh = obj.data
+ verts = mesh.verts
+
+ is_editmode = (obj.mode == 'EDIT')
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ for e in mesh.edges:
+ if e.select:
+ edges.append(e)
+
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ if len(edges) != 2:
+ operator.report({'ERROR'}, "Operator requires exactly 2 edges to be selected.")
+ return
+
+ line = LineLineIntersect(verts[edges[0].verts[0]].co, verts[edges[0].verts[1]].co, verts[edges[1].verts[0]].co, verts[edges[1].verts[1]].co)
+
+ if (line == None):
+ operator.report({'ERROR'}, "Selected edges are parallel.")
+ return
+
+ tm = obj.matrix_world.copy()
+ point = ((line[0] + line[1]) / 2)
+ point = tm * point
+
+ context.scene.cursor_location = point
+
+ return point
+
+class VIEW3D_OT_CursorToEdgeIntersection(bpy.types.Operator):
+ "Finds the mid-point of the shortest distance between two edges"
+
+ bl_idname = "view3d.snap_cursor_to_edge_intersection"
+ bl_label = "Cursor to Edge Intersection"
+
+ def poll(self, context):
+ obj = context.active_object
+ return obj != None and obj.type == 'MESH'
+
+ def execute(self, context):
+ edgeIntersect(context, self)
+ return {'FINISHED'}
+
+class VIEW3D_MT_undoS(bpy.types.Menu):
+ bl_label = "Undo/Redo"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("ed.undo", icon='TRIA_LEFT')
+ layout.operator("ed.redo", icon='TRIA_RIGHT')
+
+def register():
+ bpy.types.register(VIEW3D_MT_Space_Dynamic_Menu)
+ bpy.types.register(VIEW3D_MT_AddMenu)
+ bpy.types.register(VIEW3D_MT_TransformMenu)
+ bpy.types.register(VIEW3D_MT_MirrorMenu)
+ bpy.types.register(VIEW3D_MT_ParentMenu)
+ bpy.types.register(VIEW3D_MT_GroupMenu)
+ bpy.types.register(VIEW3D_MT_AlignMenu)
+ bpy.types.register(VIEW3D_MT_SelectMenu)
+ bpy.types.register(VIEW3D_MT_SelectEditMenu)
+ bpy.types.register(VIEW3D_MT_SelectCurveMenu)
+ bpy.types.register(VIEW3D_MT_SelectPoseMenu)
+ bpy.types.register(VIEW3D_MT_PoseCopy)
+ bpy.types.register(VIEW3D_MT_PoseNames)
+ bpy.types.register(VIEW3D_MT_KeyframeMenu)
+ bpy.types.register(VIEW3D_MT_SelectArmatureMenu)
+ bpy.types.register(VIEW3D_MT_ArmatureName)
+ bpy.types.register(VIEW3D_MT_SelectMetaball)
+ bpy.types.register(VIEW3D_MT_SelectSurface)
+ bpy.types.register(VIEW3D_MT_edit_TK)
+ bpy.types.register(VIEW3D_MT_EditArmatureTK)
+ bpy.types.register(VIEW3D_MT_editM_Edge)
+ bpy.types.register(VIEW3D_MT_EditCurveCtrlpoints)
+ bpy.types.register(VIEW3D_MT_EditCurveSegments)
+ bpy.types.register(VIEW3D_MT_EditCurveSpecials)
+ bpy.types.register(VIEW3D_OT_pivot_cursor)
+ bpy.types.register(VIEW3D_OT_revert_pivot)
+ bpy.types.register(VIEW3D_MT_CursorMenu)
+ bpy.types.register(VIEW3D_MT_EditCursorMenu)
+ bpy.types.register(VIEW3D_OT_CursorToEdgeIntersection)
+ bpy.types.register(VIEW3D_MT_undoS)
+
+ km = bpy.context.manager.active_keyconfig.keymaps['3D View']
+ kmi = km.items.add('wm.call_menu', 'SPACE', 'PRESS')
+ kmi.properties.name = "VIEW3D_MT_Space_Dynamic_Menu"
+
+
+def unregister():
+ bpy.types.unregister(VIEW3D_MT_Space_Dynamic_Menu)
+ bpy.types.unregister(VIEW3D_MT_AddMenu)
+ bpy.types.unregister(VIEW3D_MT_TransformMenu)
+ bpy.types.unregister(VIEW3D_MT_MirrorMenu)
+ bpy.types.unregister(VIEW3D_MT_ParentMenu)
+ bpy.types.unregister(VIEW3D_MT_GroupMenu)
+ bpy.types.unregister(VIEW3D_MT_AlignMenu)
+ bpy.types.unregister(VIEW3D_MT_SelectMenu)
+ bpy.types.unregister(VIEW3D_MT_SelectEditMenu)
+ bpy.types.unregister(VIEW3D_MT_SelectCurveMenu)
+ bpy.types.unregister(VIEW3D_MT_SelectPoseMenu)
+ bpy.types.unregister(VIEW3D_MT_PoseCopy)
+ bpy.types.unregister(VIEW3D_MT_PoseNames)
+ bpy.types.unregister(VIEW3D_MT_KeyframeMenu)
+ bpy.types.unregister(VIEW3D_MT_SelectArmatureMenu)
+ bpy.types.unregister(VIEW3D_MT_ArmatureName)
+ bpy.types.unregister(VIEW3D_MT_SelectMetaball)
+ bpy.types.unregister(VIEW3D_MT_SelectSurface)
+ bpy.types.unregister(VIEW3D_MT_edit_TK)
+ bpy.types.unregister(VIEW3D_MT_EditArmatureTK)
+ bpy.types.unregister(VIEW3D_MT_editM_Edge)
+ bpy.types.unregister(VIEW3D_MT_EditCurveCtrlpoints)
+ bpy.types.unregister(VIEW3D_MT_EditCurveSegments)
+ bpy.types.unregister(VIEW3D_MT_EditCurveSpecials)
+ bpy.types.unregister(VIEW3D_OT_pivot_cursor)
+ bpy.types.unregister(VIEW3D_OT_revert_pivot)
+ bpy.types.unregister(VIEW3D_MT_CursorMenu)
+ bpy.types.unregister(VIEW3D_MT_EditCursorMenu)
+ bpy.types.unregister(VIEW3D_OT_CursorToEdgeIntersection)
+ bpy.types.unregister(VIEW3D_MT_undoS)
+
+ km = bpy.context.manager.active_keyconfig.keymaps['3D View']
+ for kmi in km.items:
+ if kmi.idname == 'wm.call_menu':
+ if kmi.properties.name == "VIEW3D_MT_Space_Dynamic_Menu":
+ km.remove_item(kmi)
+ break
+
+if __name__ == "__main__":
+ register()