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>2011-01-04 21:38:03 +0300
committerLuca Bonavita <mindrones@gmail.com>2011-01-04 21:38:03 +0300
commita2d6e822841819cc937eb57c1fb0049ec0c9b04b (patch)
tree34ae89a816f8ca638c0e5365ecee7907abfb1e2e
== Blender 2.56a tags ==v2.56a
Tagging for 2.56a in tags/ and in branches/ [[Split portion of a mixed commit.]]
-rw-r--r--add_curve_aceous_galore.py1143
-rw-r--r--add_curve_torus_knots.py243
-rw-r--r--add_mesh_3d_function_surface.py635
-rw-r--r--add_mesh_BoltFactory/Boltfactory.py311
-rw-r--r--add_mesh_BoltFactory/__init__.py56
-rw-r--r--add_mesh_BoltFactory/createMesh.py2194
-rw-r--r--add_mesh_BoltFactory/preset_utils.py53
-rw-r--r--add_mesh_BoltFactory/presets/M10.py22
-rw-r--r--add_mesh_BoltFactory/presets/M12.py22
-rw-r--r--add_mesh_BoltFactory/presets/M3.py22
-rw-r--r--add_mesh_BoltFactory/presets/M4.py22
-rw-r--r--add_mesh_BoltFactory/presets/M5.py22
-rw-r--r--add_mesh_BoltFactory/presets/M6.py22
-rw-r--r--add_mesh_BoltFactory/presets/M8.py22
-rw-r--r--add_mesh_ant_landscape.py911
-rw-r--r--add_mesh_extras.py663
-rw-r--r--add_mesh_gears.py933
-rw-r--r--add_mesh_gemstones.py470
-rw-r--r--add_mesh_pipe_joint.py1306
-rw-r--r--add_mesh_solid.py901
-rw-r--r--add_mesh_twisted_torus.py362
-rw-r--r--animation_add_corrective_shape_key.py497
-rw-r--r--animation_rotobezier.py378
-rw-r--r--curve_simplify.py593
-rw-r--r--game_engine_save_as_runtime.py218
-rw-r--r--io_anim_camera.py165
-rw-r--r--io_coat3D/__init__.py225
-rw-r--r--io_coat3D/coat.py463
-rw-r--r--io_coat3D/tex.py371
-rw-r--r--io_convert_image_to_mesh_img/__init__.py128
-rw-r--r--io_convert_image_to_mesh_img/import_img.py786
-rw-r--r--io_export_directx_x.py1229
-rw-r--r--io_export_pc2.py199
-rw-r--r--io_export_unreal_psk_psa.py1784
-rw-r--r--io_import_gimp_image_to_scene.py670
-rw-r--r--io_import_images_as_planes.py347
-rw-r--r--io_import_scene_dxf.py2506
-rw-r--r--io_import_scene_lwo.py1254
-rw-r--r--io_import_scene_mhx.py2480
-rw-r--r--io_import_scene_unreal_psk.py602
-rw-r--r--io_mesh_raw/__init__.py64
-rw-r--r--io_mesh_raw/export_raw.py112
-rw-r--r--io_mesh_raw/import_raw.py142
-rw-r--r--io_mesh_stl/__init__.py142
-rw-r--r--io_mesh_stl/blender_utils.py53
-rw-r--r--io_mesh_stl/stl_utils.py242
-rw-r--r--io_scene_m3/__init__.py101
-rw-r--r--io_scene_m3/import_m3.py308
-rw-r--r--mesh_relax.py128
-rw-r--r--mesh_surface_sketch.py824
-rw-r--r--modules/add_utils.py141
-rw-r--r--modules/extensions_framework/__init__.py193
-rw-r--r--modules/extensions_framework/engine.py39
-rw-r--r--modules/extensions_framework/outputs/__init__.py26
-rw-r--r--modules/extensions_framework/outputs/xml_output.py116
-rw-r--r--modules/extensions_framework/plugin.py94
-rw-r--r--modules/extensions_framework/ui.py253
-rw-r--r--modules/extensions_framework/util.py225
-rw-r--r--modules/extensions_framework/validate.py213
-rw-r--r--object_add_chain.py150
-rw-r--r--object_animrenderbake.py184
-rw-r--r--object_cloud_gen.py751
-rw-r--r--object_fracture/__init__.py78
-rw-r--r--object_fracture/data.blendbin0 -> 232271 bytes
-rw-r--r--object_fracture/fracture_ops.py490
-rw-r--r--object_fracture/fracture_setup.py74
-rw-r--r--render_povray/__init__.py226
-rw-r--r--render_povray/render.py1447
-rw-r--r--render_povray/ui.py307
-rw-r--r--render_renderfarmfi.py966
-rw-r--r--rigify/README252
-rw-r--r--rigify/__init__.py146
-rw-r--r--rigify/generate.py351
-rw-r--r--rigify/metarig_menu.py56
-rw-r--r--rigify/metarigs/__init__.py0
-rw-r--r--rigify/metarigs/human.py1087
-rw-r--r--rigify/rig_ui_template.py96
-rw-r--r--rigify/rigs/__init__.py0
-rw-r--r--rigify/rigs/biped/__init__.py0
-rw-r--r--rigify/rigs/biped/arm/__init__.py209
-rw-r--r--rigify/rigs/biped/arm/deform.py230
-rw-r--r--rigify/rigs/biped/arm/fk.py208
-rw-r--r--rigify/rigs/biped/arm/ik.py307
-rw-r--r--rigify/rigs/biped/leg/__init__.py237
-rw-r--r--rigify/rigs/biped/leg/deform.py264
-rw-r--r--rigify/rigs/biped/leg/fk.py246
-rw-r--r--rigify/rigs/biped/leg/ik.py520
-rw-r--r--rigify/rigs/copy.py114
-rw-r--r--rigify/rigs/finger.py413
-rw-r--r--rigify/rigs/misc/__init__.py0
-rw-r--r--rigify/rigs/misc/delta.py161
-rw-r--r--rigify/rigs/neck_short.py385
-rw-r--r--rigify/rigs/palm.py273
-rw-r--r--rigify/rigs/spine.py484
-rw-r--r--rigify/ui.py251
-rw-r--r--rigify/utils.py497
-rw-r--r--space_view3d_3d_navigation.py97
-rw-r--r--space_view3d_align_tools.py338
-rw-r--r--space_view3d_copy_attributes.py773
-rw-r--r--space_view3d_materials_utils.py689
-rw-r--r--space_view3d_math_vis/__init__.py99
-rw-r--r--space_view3d_math_vis/draw.py231
-rw-r--r--space_view3d_math_vis/utils.py64
-rw-r--r--space_view3d_panel_measure.py1112
-rw-r--r--space_view3d_spacebar_menu.py1516
-rw-r--r--system_blend_info.py207
-rw-r--r--system_icon_get.py171
-rw-r--r--system_property_chart.py230
-rw-r--r--text_editor_api_navigator.py725
109 files changed, 46358 insertions, 0 deletions
diff --git a/add_curve_aceous_galore.py b/add_curve_aceous_galore.py
new file mode 100644
index 00000000..fa305e22
--- /dev/null
+++ b/add_curve_aceous_galore.py
@@ -0,0 +1,1143 @@
+# ##### 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': 'Curveaceous Galore!',
+ 'author': 'Jimmy Hazevoet, testscreenings',
+ 'version': (0,2),
+ 'blender': (2, 5, 3),
+ 'api': 32411,
+ 'location': 'Add Curve menu',
+ '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 *
+import noise as Noise
+###------------------------------------------------------------
+#### 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.seed_set(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)
+ """
+ sn = 0.001
+ rand = randnum(-100,100,Seed)
+ if Basis == 9: Basis = 14
+ vTurb = Noise.turbulence_vector((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=1.0):
+ """
+ MiscCurve( type=1, a=1.0, b=0.5, c=1.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 == 5:
+ ## Rounded Rectangle II:
+ newpoints = []
+ x = a / 2
+ y = b / 2
+ r = c / 2
+
+ if r > x:
+ r = x - 0.0001
+ if r > y:
+ r = y - 0.0001
+
+ if r>0:
+ newpoints.append([-x+r,y,0])
+ newpoints.append([x-r,y,0])
+ newpoints.append([x,y-r,0])
+ newpoints.append([x,-y+r,0])
+ newpoints.append([x-r,-y,0])
+ newpoints.append([-x+r,-y,0])
+ newpoints.append([-x,-y+r,0])
+ newpoints.append([-x,y-r,0])
+ else:
+ newpoints.append([-x,y,0])
+ newpoints.append([x,y,0])
+ newpoints.append([x,-y,0])
+ newpoints.append([-x,-y,0])
+
+ 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,t,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 = Matrix.Translation(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, self, align_matrix):
+ # options to vars
+ splineType = self.outputType # output splineType 'POLY' 'NURBS' 'BEZIER'
+ name = self.GalloreType # 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.use_endpoint_u = True
+
+ # set curveOptions
+ newCurve.dimensions = self.shape
+ newSpline.use_cyclic_u = self.use_cyclic_u
+ newSpline.use_endpoint_u = self.endp_u
+ newSpline.order_u = self.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, self.handleType)
+
+ return
+
+##------------------------------------------------------------
+# Main Function
+def main(context, self, align_matrix):
+ # deselect all objects
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # options
+ galType = self.GalloreType
+ splineType = self.outputType
+ innerRadius = self.innerRadius
+ middleRadius = self.middleRadius
+ outerRadius = self.outerRadius
+
+ # get verts
+ if galType == 'Profile':
+ verts = ProfileCurve(self.ProfileCurveType,
+ self.ProfileCurvevar1,
+ self.ProfileCurvevar2)
+ if galType == 'Miscellaneous':
+ verts = MiscCurve(self.MiscCurveType,
+ self.MiscCurvevar1,
+ self.MiscCurvevar2,
+ self.MiscCurvevar3)
+ if galType == 'Flower':
+ verts = FlowerCurve(self.petals,
+ innerRadius,
+ outerRadius,
+ self.petalWidth)
+ if galType == 'Star':
+ verts = StarCurve(self.starPoints,
+ innerRadius,
+ outerRadius,
+ self.starTwist)
+ if galType == 'Arc':
+ verts = ArcCurve(self.arcSides,
+ self.startAngle,
+ self.endAngle,
+ innerRadius,
+ outerRadius,
+ self.arcType)
+ if galType == 'Cogwheel':
+ verts = CogCurve(self.teeth,
+ innerRadius,
+ middleRadius,
+ outerRadius,
+ self.bevel)
+ if galType == 'Nsided':
+ verts = nSideCurve(self.Nsides,
+ outerRadius)
+
+ if galType == 'Splat':
+ verts = SplatCurve(self.splatSides,
+ self.splatScale,
+ self.seed,
+ self.basis,
+ outerRadius)
+
+ if galType == 'Helix':
+ verts = HelixCurve(self.helixPoints,
+ self.helixHeight,
+ self.helixStart,
+ self.helixEnd,
+ self.helixWidth,
+ self.helix_a,
+ self.helix_b)
+ if galType == 'Cycloid':
+ verts = CycloidCurve(self.cycloPoints,
+ self.cyclo_d,
+ self.cycloType,
+ self.cyclo_a,
+ self.cyclo_b,
+ self.cycloStart,
+ self.cycloEnd)
+
+ # turn verts into array
+ vertArray = vertsToPoints(verts, splineType)
+
+ # create object
+ createCurve(vertArray, self, 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")
+ use_cyclic_u = BoolProperty(name="Cyclic",
+ default=True,
+ description="make curve closed")
+ endp_u = BoolProperty(name="use_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=6, soft_max=6,
+ default=1,
+ description="Type of MiscCurve")
+ MiscCurvevar1 = FloatProperty(name="var_1",
+ default=1.0,
+ description="var1 of MiscCurve")
+ MiscCurvevar2 = FloatProperty(name="var_2",
+ default=0.5,
+ description="var2 of MiscCurve")
+ MiscCurvevar3 = FloatProperty(name="var_3",
+ default=0.1,
+ min=0, soft_min=0,
+ description="var3 of MiscCurve")
+
+ #### 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.0001, soft_min=0.0001,
+ 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,
+ max=14, soft_max=14,
+ 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):
+ layout = self.layout
+
+ # general options
+ col = layout.column()
+ col.prop(self, 'GalloreType')
+ col.label(text=self.GalloreType + " Options")
+
+ # options per GalloreType
+ box = layout.box()
+ if self.GalloreType == 'Profile':
+ box.prop(self, 'ProfileCurveType')
+ box.prop(self, 'ProfileCurvevar1')
+ box.prop(self, 'ProfileCurvevar2')
+ if self.GalloreType == 'Miscellaneous':
+ box.prop(self, 'MiscCurveType')
+ box.prop(self, 'MiscCurvevar1', text='Width')
+ box.prop(self, 'MiscCurvevar2', text='Height')
+ if self.MiscCurveType == 5:
+ box.prop(self, 'MiscCurvevar3', text='Rounded')
+ if self.GalloreType == 'Flower':
+ box.prop(self, 'petals')
+ box.prop(self, 'petalWidth')
+ box.prop(self, 'innerRadius')
+ box.prop(self, 'outerRadius')
+ if self.GalloreType == 'Star':
+ box.prop(self, 'starPoints')
+ box.prop(self, 'starTwist')
+ box.prop(self, 'innerRadius')
+ box.prop(self, 'outerRadius')
+ if self.GalloreType == 'Arc':
+ box.prop(self, 'arcSides')
+ box.prop(self, 'arcType') # has only one Type?
+ box.prop(self, 'startAngle')
+ box.prop(self, 'endAngle')
+ box.prop(self, 'innerRadius') # doesn't seem to do anything
+ box.prop(self, 'outerRadius')
+ if self.GalloreType == 'Cogwheel':
+ box.prop(self, 'teeth')
+ box.prop(self, 'bevel')
+ box.prop(self, 'innerRadius')
+ box.prop(self, 'middleRadius')
+ box.prop(self, 'outerRadius')
+ if self.GalloreType == 'Nsided':
+ box.prop(self, 'Nsides')
+ box.prop(self, 'outerRadius', text='Radius')
+
+ if self.GalloreType == 'Splat':
+ box.prop(self, 'splatSides')
+ box.prop(self, 'outerRadius')
+ box.prop(self, 'splatScale')
+ box.prop(self, 'seed')
+ box.prop(self, 'basis')
+
+ if self.GalloreType == 'Helix':
+ box.prop(self, 'helixPoints')
+ box.prop(self, 'helixHeight')
+ box.prop(self, 'helixWidth')
+ box.prop(self, 'helixStart')
+ box.prop(self, 'helixEnd')
+ box.prop(self, 'helix_a')
+ box.prop(self, 'helix_b')
+ if self.GalloreType == 'Cycloid':
+ box.prop(self, 'cycloPoints')
+ #box.prop(self, 'cycloType') # needs the other types first
+ box.prop(self, 'cycloStart')
+ box.prop(self, 'cycloEnd')
+ box.prop(self, 'cyclo_a')
+ box.prop(self, 'cyclo_b')
+ box.prop(self, 'cyclo_d')
+
+ col = layout.column()
+ col.label(text="Output Curve Type")
+ row = layout.row()
+ row.prop(self, 'outputType', expand=True)
+ col = layout.column()
+ col.label(text="Curve Options")
+
+ # output options
+ box = layout.box()
+ if self.outputType == 'NURBS':
+ box.row().prop(self, 'shape', expand=True)
+ #box.prop(self, 'use_cyclic_u')
+ #box.prop(self, 'endp_u')
+ box.prop(self, 'order_u')
+
+ if self.outputType == 'POLY':
+ box.row().prop(self, 'shape', expand=True)
+ #box.prop(self, 'use_cyclic_u')
+
+ if self.outputType == 'BEZIER':
+ box.row().prop(self, 'shape', expand=True)
+ box.row().prop(self, 'handleType', expand=True)
+ #box.prop(self, 'use_cyclic_u')
+
+
+ ##### POLL #####
+ @classmethod
+ def poll(cls, context):
+ return context.scene != None
+
+ ##### EXECUTE #####
+ def execute(self, context):
+ # turn off undo
+ undo = bpy.context.user_preferences.edit.use_global_undo
+ bpy.context.user_preferences.edit.use_global_undo = False
+
+ # deal with 2D - 3D curve differences
+ if self.GalloreType in ['Helix', 'Cycloid']:
+ self.shape = '3D'
+ #else:
+ #self.shape = '2D' # someone decide if we want this
+
+ if self.GalloreType in ['Helix']:
+ self.use_cyclic_u = False
+ else:
+ self.use_cyclic_u = True
+
+
+ # main function
+ main(context, self, self.align_matrix)
+
+ # restore pre operator undo state
+ bpy.context.user_preferences.edit.use_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 #####
+
+def Curveaceous_galore_button(self, context):
+ self.layout.operator(Curveaceous_galore.bl_idname, text="curvatures gallore", icon="PLUGIN")
+
+
+def register():
+ bpy.types.INFO_MT_curve_add.append(Curveaceous_galore_button)
+
+def unregister():
+ 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..1a298488
--- /dev/null
+++ b/add_curve_torus_knots.py
@@ -0,0 +1,243 @@
+# ##### 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": "Torus Knots",
+ "author": "testscreenings",
+ "version": (0,1),
+ "blender": (2, 5, 5),
+ "api": 33754,
+ "location": "View3D > Add > Curve",
+ "description": "Adds many types of (torus) knots",
+ "warning": "",
+ "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 math import sin, cos, pi
+from add_utils import *
+
+
+########################################################################
+####################### Knot Definitions ###############################
+########################################################################
+def Torus_Knot(self):
+ p = self.torus_p
+ q = self.torus_q
+ w = self.torus_w
+ res = self.torus_res
+ h = self.torus_h
+ u = self.torus_u
+ v = self.torus_v
+ rounds = self.torus_rounds
+
+ 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.extend([x,y,z,1])
+
+ return newPoints
+
+
+##------------------------------------------------------------
+# Main Function
+def create_torus_knot(self, context):
+ verts = Torus_Knot(self)
+
+ curve_data = bpy.data.curves.new(name='Torus Knot', type='CURVE')
+ spline = curve_data.splines.new(type='NURBS')
+ spline.points.add(int(len(verts)*0.25 - 1))
+ spline.points.foreach_set('co', verts)
+ spline.use_endpoint_u = True
+ spline.use_cyclic_u = True
+ spline.order_u = 4
+ curve_data.dimensions = '3D'
+
+ if self.geo_surf:
+ curve_data.bevel_depth = self.geo_bDepth
+ curve_data.bevel_resolution = self.geo_bRes
+ curve_data.use_fill_front = False
+ curve_data.use_fill_back = False
+ curve_data.extrude = self.geo_extrude
+ #curve_data.offset = self.geo_width # removed, somehow screws things up all of a sudden
+ curve_data.resolution_u = self.geo_res
+
+ new_obj = add_object_data(context, curve_data, operator=self)
+
+
+class torus_knot_plus(bpy.types.Operator, AddObjectHelper):
+ ''''''
+ bl_idname = "curve.torus_knot_plus"
+ bl_label = "Torus Knot +"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = "adds many types of knots"
+
+ #### 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_res = IntProperty(name="resolution",
+ default=12,
+ min=1, soft_min=1)
+
+
+ #### Parameters
+ torus_res = IntProperty(name="Resoulution",
+ default=100,
+ 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_rounds = IntProperty(name="Rounds",
+ default=2,
+ min=1, soft_min=1,
+ #max=1, soft_max=1,
+ description="Rounds")
+
+ ##### DRAW #####
+ def draw(self, context):
+ layout = self.layout
+
+ # general options
+ col = layout.column()
+ col.label(text="Torus Knot Parameters")
+
+ # Parameters
+ box = layout.box()
+ box.prop(self, 'torus_res')
+ box.prop(self, 'torus_w')
+ box.prop(self, 'torus_h')
+ box.prop(self, 'torus_p')
+ box.prop(self, 'torus_q')
+ box.prop(self, 'options_plus')
+ if self.options_plus:
+ box.prop(self, 'torus_u')
+ box.prop(self, 'torus_v')
+ box.prop(self, 'torus_rounds')
+
+ # surface Options
+ col = layout.column()
+ col.label(text="Geometry Options")
+ box = layout.box()
+ box.prop(self, 'geo_surf')
+ if self.geo_surf:
+ box.prop(self, 'geo_bDepth')
+ box.prop(self, 'geo_bRes')
+ box.prop(self, 'geo_extrude')
+ box.prop(self, 'geo_res')
+ col = layout.column()
+ col.prop(self, 'location')
+ col.prop(self, 'rotation')
+
+ ##### POLL #####
+ @classmethod
+ def poll(cls, context):
+ return context.scene != None
+
+ ##### EXECUTE #####
+ def execute(self, context):
+ # turn off undo
+ undo = bpy.context.user_preferences.edit.use_global_undo
+ bpy.context.user_preferences.edit.use_global_undo = False
+
+ if not self.options_plus:
+ self.torus_rounds = self.torus_p
+
+ #recoded for add_utils
+ create_torus_knot(self, context)
+
+ # restore pre operator undo state
+ bpy.context.user_preferences.edit.use_global_undo = undo
+
+ return {'FINISHED'}
+
+################################################################################
+##### REGISTER #####
+
+def torus_knot_plus_button(self, context):
+ self.layout.operator(torus_knot_plus.bl_idname, text="Torus Knot +", icon="PLUGIN")
+
+
+def register():
+ bpy.types.INFO_MT_curve_add.append(torus_knot_plus_button)
+
+def unregister():
+ 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..38d570b3
--- /dev/null
+++ b/add_mesh_3d_function_surface.py
@@ -0,0 +1,635 @@
+# ##### 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": "3D Function Surfaces",
+ "author": "Buerbaum Martin (Pontiac)",
+ "version": (0,3,5),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "View3D > Add > Mesh > Z Function Surface & XYZ Function Surface",
+ "description": "Create Objects using Math Formulas",
+ "warning": "",
+ "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"}
+
+"""
+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
+"""
+
+
+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 = Matrix.Translation(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.edit
+ equation = self.equation
+ div_x = self.div_x
+ div_y = self.div_y
+ size_x = self.size_x
+ size_y = self.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):
+
+ verts, faces = xyz_function_surface_faces(
+ self,
+ self.x_eq,
+ self.y_eq,
+ self.z_eq,
+ self.range_u_min,
+ self.range_u_max,
+ self.range_u_step,
+ self.wrap_u,
+ self.range_v_min,
+ self.range_v_max,
+ self.range_v_step,
+ self.wrap_v)
+
+ if not verts:
+ return {'CANCELLED'}
+
+ obj = create_mesh_object(context, verts, [], faces,
+ "XYZ Function", self.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
+def menu_func_z(self, context):
+ self.layout.operator(AddZFunctionSurface.bl_idname, text="Z Function Surface", icon="PLUGIN")
+
+def menu_func_xyz(self, context):
+ self.layout.operator(AddXYZFunctionSurface.bl_idname, text="X,Y,Z Function Surface", icon="PLUGIN")
+
+
+def register():
+ # 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():
+ # 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_BoltFactory/Boltfactory.py b/add_mesh_BoltFactory/Boltfactory.py
new file mode 100644
index 00000000..e2e396c7
--- /dev/null
+++ b/add_mesh_BoltFactory/Boltfactory.py
@@ -0,0 +1,311 @@
+# ##### 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
+import mathutils
+from bpy.props import *
+from add_mesh_BoltFactory.createMesh import *
+from add_mesh_BoltFactory.preset_utils import *
+
+
+
+##------------------------------------------------------------
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = mathutils.Matrix.Translation(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
+
+
+
+class add_mesh_bolt(bpy.types.Operator):
+ ''''''
+ bl_idname = 'add_mesh_bolt'
+ bl_label = "Add Bolt"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = "adds many types of Bolts"
+
+ align_matrix = mathutils.Matrix()
+ MAX_INPUT_NUMBER = 50
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+
+ #Model Types
+ Model_Type_List = [('bf_Model_Bolt','BOLT','Bolt Model'),
+ ('bf_Model_Nut','NUT','Nut Model')]
+ bf_Model_Type = EnumProperty( attr='bf_Model_Type',
+ name='Model',
+ description='Choose the type off model you would like',
+ items = Model_Type_List, default = 'bf_Model_Bolt')
+
+ #Head Types
+ Model_Type_List = [('bf_Head_Hex','HEX','Hex Head'),
+ ('bf_Head_Cap','CAP','Cap Head'),
+ ('bf_Head_Dome','DOME','Dome Head'),
+ ('bf_Head_Pan','PAN','Pan Head'),
+ ('bf_Head_CounterSink','COUNTER SINK','Counter Sink Head')]
+ bf_Head_Type = EnumProperty( attr='bf_Head_Type',
+ name='Head',
+ description='Choose the type off Head you would like',
+ items = Model_Type_List, default = 'bf_Head_Hex')
+
+ #Bit Types
+ Bit_Type_List = [('bf_Bit_None','NONE','No Bit Type'),
+ ('bf_Bit_Allen','ALLEN','Allen Bit Type'),
+ ('bf_Bit_Philips','PHILLIPS','Phillips Bit Type')]
+ bf_Bit_Type = EnumProperty( attr='bf_Bit_Type',
+ name='Bit Type',
+ description='Choose the type of bit to you would like',
+ items = Bit_Type_List, default = 'bf_Bit_None')
+
+ #Nut Types
+ Nut_Type_List = [('bf_Nut_Hex','HEX','Hex Nut'),
+ ('bf_Nut_Lock','LOCK','Lock Nut')]
+ bf_Nut_Type = EnumProperty( attr='bf_Nut_Type',
+ name='Nut Type',
+ description='Choose the type of nut you would like',
+ items = Nut_Type_List, default = 'bf_Nut_Hex')
+
+ #Shank Types
+ bf_Shank_Length = FloatProperty(attr='bf_Shank_Length',
+ name='Shank Length', default = 0,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Length of the unthreaded shank')
+
+ bf_Shank_Dia = FloatProperty(attr='bf_Shank_Dia',
+ name='Shank Dia', default = 3,
+ min = 0, soft_min = 0,max = MAX_INPUT_NUMBER,
+ description='Diameter of the shank')
+
+ bf_Phillips_Bit_Depth = FloatProperty(attr='bf_Phillips_Bit_Depth',
+ name='Bit Depth', default = 0, #set in execute
+ options = {'HIDDEN'}, #gets calculated in execute
+ min = 0, soft_min = 0,max = MAX_INPUT_NUMBER,
+ description='Depth of the Phillips Bit')
+
+ bf_Allen_Bit_Depth = FloatProperty(attr='bf_Allen_Bit_Depth',
+ name='Bit Depth', default = 1.5,
+ min = 0, soft_min = 0,max = MAX_INPUT_NUMBER,
+ description='Depth of the Allen Bit')
+
+ bf_Allen_Bit_Flat_Distance = FloatProperty( attr='bf_Allen_Bit_Flat_Distance',
+ name='Flat Dist', default = 2.5,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Flat Distance of the Allen Bit')
+
+ bf_Hex_Head_Height = FloatProperty( attr='bf_Hex_Head_Height',
+ name='Head Height', default = 2,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Height of the Hex Head')
+
+ bf_Hex_Head_Flat_Distance = FloatProperty( attr='bf_Hex_Head_Flat_Distance',
+ name='Flat Dist', default = 5.5,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Flat Distance of the Hex Head')
+
+ bf_CounterSink_Head_Dia = FloatProperty( attr='bf_CounterSink_Head_Dia',
+ name='Head Dia', default = 5.5,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Diameter of the Counter Sink Head')
+
+ bf_Cap_Head_Height = FloatProperty( attr='bf_Cap_Head_Height',
+ name='Head Height', default = 5.5,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Height of the Cap Head')
+
+ bf_Cap_Head_Dia = FloatProperty( attr='bf_Cap_Head_Dia',
+ name='Head Dia', default = 3,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Diameter of the Cap Head')
+
+ bf_Dome_Head_Dia = FloatProperty( attr='bf_Dome_Head_Dia',
+ name='Dome Head Dia', default = 5.6,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Length of the unthreaded shank')
+
+ bf_Pan_Head_Dia = FloatProperty( attr='bf_Pan_Head_Dia',
+ name='Pan Head Dia', default = 5.6,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Diameter of the Pan Head')
+
+ bf_Philips_Bit_Dia = FloatProperty(attr='bf_Philips_Bit_Dia',
+ name='Bit Dia', default = 0, #set in execute
+ options = {'HIDDEN'}, #gets calculated in execute
+ min = 0, soft_min = 0,max = MAX_INPUT_NUMBER,
+ description='Diameter of the Philips Bit')
+
+ bf_Thread_Length = FloatProperty( attr='bf_Thread_Length',
+ name='Thread Length', default = 6,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Length of the Thread')
+
+ bf_Major_Dia = FloatProperty( attr='bf_Major_Dia',
+ name='Major Dia', default = 3,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Outside diameter of the Thread')
+
+ bf_Pitch = FloatProperty( attr='bf_Pitch',
+ name='Pitch', default = 0.35,
+ min = 0.1, soft_min = 0.1, max = 7.0,
+ description='Pitch if the thread')
+
+ bf_Minor_Dia = FloatProperty( attr='bf_Minor_Dia',
+ name='Minor Dia', default = 0, #set in execute
+ options = {'HIDDEN'}, #gets calculated in execute
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Inside diameter of the Thread')
+
+ bf_Crest_Percent = IntProperty( attr='bf_Crest_Percent',
+ name='Crest Percent', default = 10,
+ min = 1, soft_min = 1, max = 90,
+ description='Percent of the pitch that makes up the Crest')
+
+ bf_Root_Percent = IntProperty( attr='bf_Root_Percent',
+ name='Root Percent', default = 10,
+ min = 1, soft_min = 1, max = 90,
+ description='Percent of the pitch that makes up the Root')
+
+ bf_Hex_Nut_Height = FloatProperty( attr='bf_Hex_Nut_Height',
+ name='Hex Nut Height', default = 2.4,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Height of the Hex Nut')
+
+ bf_Hex_Nut_Flat_Distance = FloatProperty( attr='bf_Hex_Nut_Flat_Distance',
+ name='Hex Nut Flat Dist', default = 5.5,
+ min = 0, soft_min = 0, max = MAX_INPUT_NUMBER,
+ description='Flat distance of the Hex Nut')
+
+ presets, presetsPath = getPresets()
+
+ bf_presets = EnumProperty(attr='bf_presets',
+ name='Preset',
+ description="Use Preset from File",
+ default='M3.py',
+ items=presets)
+
+ last_preset = None
+
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column()
+
+ #ENUMS
+ col.prop(self, 'bf_Model_Type')
+ col.prop(self, 'bf_presets')
+ col.separator()
+
+ #Bit
+ if self.bf_Model_Type == 'bf_Model_Bolt':
+ col.prop(self, 'bf_Bit_Type')
+ if self.bf_Bit_Type == 'bf_Bit_None':
+ DoNothing = 1;
+ elif self.bf_Bit_Type == 'bf_Bit_Allen':
+ col.prop(self, 'bf_Allen_Bit_Depth')
+ col.prop(self, 'bf_Allen_Bit_Flat_Distance')
+ elif self.bf_Bit_Type == 'bf_Bit_Philips':
+ col.prop(self, 'bf_Phillips_Bit_Depth')
+ col.prop(self, 'bf_Philips_Bit_Dia')
+ col.separator()
+
+ #Head
+ if self.bf_Model_Type == 'bf_Model_Bolt':
+ col.prop(self, 'bf_Head_Type')
+ if self.bf_Head_Type == 'bf_Head_Hex':
+ col.prop(self, 'bf_Hex_Head_Height')
+ col.prop(self, 'bf_Hex_Head_Flat_Distance')
+ elif self.bf_Head_Type == 'bf_Head_Cap':
+ col.prop(self, 'bf_Cap_Head_Height')
+ col.prop(self, 'bf_Cap_Head_Dia')
+ elif self.bf_Head_Type == 'bf_Head_Dome':
+ col.prop(self, 'bf_Dome_Head_Dia')
+ elif self.bf_Head_Type == 'bf_Head_Pan':
+ col.prop(self, 'bf_Pan_Head_Dia')
+ elif self.bf_Head_Type == 'bf_Head_CounterSink':
+ col.prop(self, 'bf_CounterSink_Head_Dia')
+ col.separator()
+ #Shank
+ if self.bf_Model_Type == 'bf_Model_Bolt':
+ col.label(text='Shank')
+ col.prop(self, 'bf_Shank_Length')
+ col.prop(self, 'bf_Shank_Dia')
+ col.separator()
+ #Nut
+ if self.bf_Model_Type == 'bf_Model_Nut':
+ col.prop(self, 'bf_Nut_Type')
+ col.prop(self, 'bf_Hex_Nut_Height')
+ col.prop(self, 'bf_Hex_Nut_Flat_Distance')
+ #Thread
+ col.label(text='Thread')
+ if self.bf_Model_Type == 'bf_Model_Bolt':
+ col.prop(self, 'bf_Thread_Length')
+ col.prop(self, 'bf_Major_Dia')
+ col.prop(self, 'bf_Minor_Dia')
+ col.prop(self, 'bf_Pitch')
+ col.prop(self, 'bf_Crest_Percent')
+ col.prop(self, 'bf_Root_Percent')
+
+
+
+ ##### POLL #####
+ @classmethod
+ def poll(cls, context):
+ return context.scene != None
+
+ ##### EXECUTE #####
+ def execute(self, context):
+
+ #print('EXECUTING...')
+
+ if not self.last_preset or self.bf_presets != self.last_preset:
+ #print('setting Preset', self.bf_presets)
+ setProps(self, self.bf_presets, self.presetsPath)
+ self.bf_Phillips_Bit_Depth = float(Get_Phillips_Bit_Height(self.bf_Philips_Bit_Dia))
+
+ self.last_preset = self.bf_presets
+
+
+ #self.bf_Phillips_Bit_Depth = float(Get_Phillips_Bit_Height(self.bf_Philips_Bit_Dia))
+ #self.bf_Philips_Bit_Dia = self.bf_Pan_Head_Dia*(1.82/5.6)
+ #self.bf_Minor_Dia = self.bf_Major_Dia - (1.082532 * self.bf_Pitch)
+
+ Create_New_Mesh(self, context, self.align_matrix)
+
+ return {'FINISHED'}
+
+ ##### INVOKE #####
+ def invoke(self, context, event):
+ #print('\n___________START_____________')
+ # store creation_matrix
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+
+ return {'FINISHED'}
diff --git a/add_mesh_BoltFactory/__init__.py b/add_mesh_BoltFactory/__init__.py
new file mode 100644
index 00000000..784faa0c
--- /dev/null
+++ b/add_mesh_BoltFactory/__init__.py
@@ -0,0 +1,56 @@
+# ##### 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": "BoltFactory",
+ "author": "Aaron Keith",
+ "version": (3,9),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "add Mesh",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Add_Mesh/BoltFactory",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=22842&group_id=153&atid=468",
+ "category": "Add Mesh"}
+
+if "bpy" in locals():
+ import imp
+ imp.reload(Boltfactory)
+else:
+ from add_mesh_BoltFactory import Boltfactory
+
+import bpy
+
+################################################################################
+##### REGISTER #####
+
+def add_mesh_bolt_button(self, context):
+ self.layout.operator(Boltfactory.add_mesh_bolt.bl_idname, text="Bolt", icon="PLUGIN")
+
+
+def register():
+ bpy.types.INFO_MT_mesh_add.append(add_mesh_bolt_button)
+ #bpy.types.VIEW3D_PT_tools_objectmode.prepend(add_mesh_bolt_button) #just for testing
+
+def unregister():
+ bpy.types.INFO_MT_mesh_add.remove(add_mesh_bolt_button)
+ #bpy.types.VIEW3D_PT_tools_objectmode.remove(add_mesh_bolt_button) #just for testing
+
+if __name__ == "__main__":
+ register()
diff --git a/add_mesh_BoltFactory/createMesh.py b/add_mesh_BoltFactory/createMesh.py
new file mode 100644
index 00000000..87b5bc39
--- /dev/null
+++ b/add_mesh_BoltFactory/createMesh.py
@@ -0,0 +1,2194 @@
+# ##### 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 os #remove this
+import bpy
+
+try:
+ import mathutils
+ MATHUTILS = mathutils
+except:
+ import Mathutils
+ MATHUTILS = Mathutils
+
+
+
+from math import *
+from bpy.props import IntProperty, FloatProperty ,EnumProperty
+from itertools import *
+
+NARROW_UI = 180
+MAX_INPUT_NUMBER = 50
+
+#Global_Scale = 0.001 #1 blender unit = X mm
+GLOBAL_SCALE = 0.1 #1 blender unit = X mm
+#Global_Scale = 1.0 #1 blender unit = X mm
+
+
+
+
+# next two utility functions are stolen from import_obj.py
+
+def unpack_list(list_of_tuples):
+ l = []
+ for t in list_of_tuples:
+ l.extend(t)
+ return l
+
+def unpack_face_list(list_of_tuples):
+ l = []
+ for t in list_of_tuples:
+ face = [i for i in t]
+
+ if len(face) != 3 and len(face) != 4:
+ raise RuntimeError("{0} vertices in face.".format(len(face)))
+
+ # rotate indices if the 4th is 0
+ if len(face) == 4 and face[3] == 0:
+ face = [face[3], face[0], face[1], face[2]]
+
+ if len(face) == 3:
+ face.append(0)
+
+ l.extend(face)
+
+ return l
+
+'''
+Remove Doubles takes a list on Verts and a list of Faces and
+removes the doubles, much like Blender does in edit mode.
+It doesn’t have the range function but it will round the corrdinates
+and remove verts that are very close togther. The function
+is useful because you can perform a “Remove Doubles” with out
+having to enter Edit Mode. Having to enter edit mode has the
+disadvantage of not being able to interactively change the properties.
+'''
+
+
+def RemoveDoubles(verts,faces,Decimal_Places = 4):
+
+ new_verts = []
+ new_faces = []
+ dict_verts = {}
+ Rounded_Verts = []
+
+ for v in verts:
+ Rounded_Verts.append([round(v[0],Decimal_Places),round(v[1],Decimal_Places),round(v[2],Decimal_Places)])
+
+ for face in faces:
+ new_face = []
+ for vert_index in face:
+ Real_co = tuple(verts[vert_index])
+ Rounded_co = tuple(Rounded_Verts[vert_index])
+
+ if Rounded_co not in dict_verts:
+ dict_verts[Rounded_co] = len(dict_verts)
+ new_verts.append(Real_co)
+ if dict_verts[Rounded_co] not in new_face:
+ new_face.append(dict_verts[Rounded_co])
+ if len(new_face) == 3 or len(new_face) == 4:
+ new_faces.append(new_face)
+
+ return new_verts,new_faces
+
+
+
+
+def Scale_Mesh_Verts(verts,scale_factor):
+ Ret_verts = []
+ for v in verts:
+ Ret_verts.append([v[0]*scale_factor,v[1]*scale_factor,v[2]*scale_factor])
+ return Ret_verts
+
+
+
+
+
+#Create a matrix representing a rotation.
+#
+#Parameters:
+#
+# * angle (float) - The angle of rotation desired.
+# * matSize (int) - The size of the rotation matrix to construct. Can be 2d, 3d, or 4d.
+# * axisFlag (string (optional)) - Possible values:
+# o "x - x-axis rotation"
+# o "y - y-axis rotation"
+# o "z - z-axis rotation"
+# o "r - arbitrary rotation around vector"
+# * axis (Vector object. (optional)) - The arbitrary axis of rotation used with "R"
+#
+#Returns: Matrix object.
+# A new rotation matrix.
+def Simple_RotationMatrix(angle, matSize, axisFlag):
+ if matSize != 4 :
+ print ("Simple_RotationMatrix can only do 4x4")
+
+ q = radians(angle) #make the rotation go clockwise
+
+ if axisFlag == 'x':
+ matrix = MATHUTILS.Matrix([1,0,0,0],[0,cos(q),sin(q),0],[0,-sin(q),cos(q),0],[0,0,0,1])
+ elif axisFlag == 'y':
+ matrix = MATHUTILS.Matrix([cos(q),0,-sin(q),0],[0,1,0,0],[sin(q),0,cos(q),0],[0,0,0,1])
+ elif axisFlag == 'z':
+ matrix = MATHUTILS.Matrix([cos(q),sin(q),0,0],[-sin(q),cos(q),0,0],[0,0,1,0],[0,0,0,1])
+ else:
+ print ("Simple_RotationMatrix can only do x y z axis")
+ return matrix
+
+
+##########################################################################################
+##########################################################################################
+## Converter Functions For Bolt Factory
+##########################################################################################
+##########################################################################################
+
+
+def Flat_To_Radius(FLAT):
+ h = (float(FLAT)/2)/cos(radians(30))
+ return h
+
+def Get_Phillips_Bit_Height(Bit_Dia):
+ Flat_Width_half = (Bit_Dia*(0.5/1.82))/2.0
+ Bit_Rad = Bit_Dia / 2.0
+ x = Bit_Rad - Flat_Width_half
+ y = tan(radians(60))*x
+ return float(y)
+
+
+##########################################################################################
+##########################################################################################
+## Miscellaneous Utilities
+##########################################################################################
+##########################################################################################
+
+# Returns a list of verts rotated by the given matrix. Used by SpinDup
+def Rot_Mesh(verts,matrix):
+ ret = []
+ #print ("rot mat",matrix)
+ for v in verts:
+ vec = MATHUTILS.Vector(v) * matrix
+ ret.append([vec.x,vec.y,vec.z])
+ return ret
+
+# Returns a list of faces that has there index incremented by offset
+def Copy_Faces(faces,offset):
+ ret = []
+ for f in faces:
+ fsub = []
+ for i in range(len(f)):
+ fsub.append(f[i]+ offset)
+ ret.append(fsub)
+ return ret
+
+
+# Much like Blenders built in SpinDup.
+def SpinDup(VERTS,FACES,DEGREE,DIVISIONS,AXIS):
+ verts=[]
+ faces=[]
+
+ if DIVISIONS == 0:
+ DIVISIONS = 1
+
+ step = DEGREE/DIVISIONS # set step so pieces * step = degrees in arc
+
+ for i in range(int(DIVISIONS)):
+ rotmat = Simple_RotationMatrix(step*i, 4, AXIS) # 4x4 rotation matrix, 30d about the x axis.
+ Rot = Rot_Mesh(VERTS,rotmat)
+ faces.extend(Copy_Faces(FACES,len(verts)))
+ verts.extend(Rot)
+ return verts,faces
+
+
+
+# Returns a list of verts that have been moved up the z axis by DISTANCE
+def Move_Verts_Up_Z(VERTS,DISTANCE):
+ ret = []
+ for v in VERTS:
+ ret.append([v[0],v[1],v[2]+DISTANCE])
+ return ret
+
+
+# Returns a list of verts and faces that has been mirrored in the AXIS
+def Mirror_Verts_Faces(VERTS,FACES,AXIS,FLIP_POINT =0):
+ ret_vert = []
+ ret_face = []
+ offset = len(VERTS)
+ if AXIS == 'y':
+ for v in VERTS:
+ Delta = v[0] - FLIP_POINT
+ ret_vert.append([FLIP_POINT-Delta,v[1],v[2]])
+ if AXIS == 'x':
+ for v in VERTS:
+ Delta = v[1] - FLIP_POINT
+ ret_vert.append([v[0],FLIP_POINT-Delta,v[2]])
+ if AXIS == 'z':
+ for v in VERTS:
+ Delta = v[2] - FLIP_POINT
+ ret_vert.append([v[0],v[1],FLIP_POINT-Delta])
+
+ for f in FACES:
+ fsub = []
+ for i in range(len(f)):
+ fsub.append(f[i]+ offset)
+ fsub.reverse() # flip the order to make norm point out
+ ret_face.append(fsub)
+
+ return ret_vert,ret_face
+
+
+
+# Returns a list of faces that
+# make up an array of 4 point polygon.
+def Build_Face_List_Quads(OFFSET,COLUM,ROW,FLIP = 0):
+ Ret =[]
+ RowStart = 0;
+ for j in range(ROW):
+ for i in range(COLUM):
+ Res1 = RowStart + i;
+ Res2 = RowStart + i + (COLUM +1)
+ Res3 = RowStart + i + (COLUM +1) +1
+ Res4 = RowStart+i+1
+ if FLIP:
+ Ret.append([OFFSET+Res1,OFFSET+Res2,OFFSET+Res3,OFFSET+Res4])
+ else:
+ Ret.append([OFFSET+Res4,OFFSET+Res3,OFFSET+Res2,OFFSET+Res1])
+ RowStart += COLUM+1
+ return Ret
+
+
+# Returns a list of faces that makes up a fill pattern for a
+# circle
+def Fill_Ring_Face(OFFSET,NUM,FACE_DOWN = 0):
+ Ret =[]
+ Face = [1,2,0]
+ TempFace = [0,0,0]
+ A = 0
+ B = 1
+ C = 2
+ if NUM < 3:
+ return None
+ for i in range(NUM-2):
+ if (i%2):
+ TempFace[0] = Face[C];
+ TempFace[1] = Face[C] + 1;
+ TempFace[2] = Face[B];
+ if FACE_DOWN:
+ Ret.append([OFFSET+Face[2],OFFSET+Face[1],OFFSET+Face[0]])
+ else:
+ Ret.append([OFFSET+Face[0],OFFSET+Face[1],OFFSET+Face[2]])
+ else:
+ TempFace[0] =Face[C];
+ if Face[C] == 0:
+ TempFace[1] = NUM-1;
+ else:
+ TempFace[1] = Face[C] - 1;
+ TempFace[2] = Face[B];
+ if FACE_DOWN:
+ Ret.append([OFFSET+Face[0],OFFSET+Face[1],OFFSET+Face[2]])
+ else:
+ Ret.append([OFFSET+Face[2],OFFSET+Face[1],OFFSET+Face[0]])
+
+ Face[0] = TempFace[0]
+ Face[1] = TempFace[1]
+ Face[2] = TempFace[2]
+ return Ret
+
+######################################################################################
+##########################################################################################
+##########################################################################################
+## Create Allen Bit
+##########################################################################################
+##########################################################################################
+
+
+def Allen_Fill(OFFSET,FLIP= 0):
+ faces = []
+ Lookup = [[19,1,0],
+ [19,2,1],
+ [19,3,2],
+ [19,20,3],
+ [20,4,3],
+ [20,5,4],
+ [20,6,5],
+ [20,7,6],
+ [20,8,7],
+ [20,9,8],
+
+ [20,21,9],
+
+ [21,10,9],
+ [21,11,10],
+ [21,12,11],
+ [21,13,12],
+ [21,14,13],
+ [21,15,14],
+
+ [21,22,15],
+ [22,16,15],
+ [22,17,16],
+ [22,18,17]
+ ]
+ for i in Lookup:
+ if FLIP:
+ faces.append([OFFSET+i[2],OFFSET+i[1],OFFSET+i[0]])
+ else:
+ faces.append([OFFSET+i[0],OFFSET+i[1],OFFSET+i[2]])
+
+ return faces
+
+def Allen_Bit_Dia(FLAT_DISTANCE):
+ Flat_Radius = (float(FLAT_DISTANCE)/2.0)/cos(radians(30))
+ return (Flat_Radius * 1.05) * 2.0
+
+def Allen_Bit_Dia_To_Flat(DIA):
+ Flat_Radius = (DIA/2.0)/1.05
+ return (Flat_Radius * cos (radians(30)))* 2.0
+
+
+
+def Create_Allen_Bit(FLAT_DISTANCE,HEIGHT):
+ Div = 36
+ verts = []
+ faces = []
+
+ Flat_Radius = (float(FLAT_DISTANCE)/2.0)/cos(radians(30))
+ OUTTER_RADIUS = Flat_Radius * 1.05
+ Outter_Radius_Height = Flat_Radius * (0.1/5.77)
+ FaceStart_Outside = len(verts)
+ Deg_Step = 360.0 /float(Div)
+
+ for i in range(int(Div/2)+1): # only do half and mirror later
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,0])
+
+ FaceStart_Inside = len(verts)
+
+ Deg_Step = 360.0 /float(6)
+ for i in range(int(6/2)+1):
+ x = sin(radians(i*Deg_Step))* Flat_Radius
+ y = cos(radians(i*Deg_Step))* Flat_Radius
+ verts.append([x,y,0-Outter_Radius_Height])
+
+ faces.extend(Allen_Fill(FaceStart_Outside,0))
+
+
+ FaceStart_Bottom = len(verts)
+
+ Deg_Step = 360.0 /float(6)
+ for i in range(int(6/2)+1):
+ x = sin(radians(i*Deg_Step))* Flat_Radius
+ y = cos(radians(i*Deg_Step))* Flat_Radius
+ verts.append([x,y,0-HEIGHT])
+
+ faces.extend(Build_Face_List_Quads(FaceStart_Inside,3,1,True))
+ faces.extend(Fill_Ring_Face(FaceStart_Bottom,4))
+
+
+ M_Verts,M_Faces = Mirror_Verts_Faces(verts,faces,'y')
+ verts.extend(M_Verts)
+ faces.extend(M_Faces)
+
+ return verts,faces,OUTTER_RADIUS * 2.0
+
+
+##########################################################################################
+##########################################################################################
+## Create Phillips Bit
+##########################################################################################
+##########################################################################################
+
+
+def Phillips_Fill(OFFSET,FLIP= 0):
+ faces = []
+ Lookup = [[0,1,10],
+ [1,11,10],
+ [1,2,11],
+ [2,12,11],
+
+ [2,3,12],
+ [3,4,12],
+ [4,5,12],
+ [5,6,12],
+ [6,7,12],
+
+ [7,13,12],
+ [7,8,13],
+ [8,14,13],
+ [8,9,14],
+
+
+ [10,11,16,15],
+ [11,12,16],
+ [12,13,16],
+ [13,14,17,16],
+ [15,16,17,18]
+
+
+ ]
+ for i in Lookup:
+ if FLIP:
+ if len(i) == 3:
+ faces.append([OFFSET+i[2],OFFSET+i[1],OFFSET+i[0]])
+ else:
+ faces.append([OFFSET+i[3],OFFSET+i[2],OFFSET+i[1],OFFSET+i[0]])
+ else:
+ if len(i) == 3:
+ faces.append([OFFSET+i[0],OFFSET+i[1],OFFSET+i[2]])
+ else:
+ faces.append([OFFSET+i[0],OFFSET+i[1],OFFSET+i[2],OFFSET+i[3]])
+ return faces
+
+
+
+def Create_Phillips_Bit(FLAT_DIA,FLAT_WIDTH,HEIGHT):
+ Div = 36
+ verts = []
+ faces = []
+
+ FLAT_RADIUS = FLAT_DIA * 0.5
+ OUTTER_RADIUS = FLAT_RADIUS * 1.05
+
+ Flat_Half = float(FLAT_WIDTH)/2.0
+
+ FaceStart_Outside = len(verts)
+ Deg_Step = 360.0 /float(Div)
+ for i in range(int(Div/4)+1): # only do half and mirror later
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,0])
+
+
+ FaceStart_Inside = len(verts)
+ verts.append([0,FLAT_RADIUS,0]) #10
+ verts.append([Flat_Half,FLAT_RADIUS,0]) #11
+ verts.append([Flat_Half,Flat_Half,0]) #12
+ verts.append([FLAT_RADIUS,Flat_Half,0]) #13
+ verts.append([FLAT_RADIUS,0,0]) #14
+
+
+ verts.append([0,Flat_Half,0-HEIGHT]) #15
+ verts.append([Flat_Half,Flat_Half,0-HEIGHT]) #16
+ verts.append([Flat_Half,0,0-HEIGHT]) #17
+
+ verts.append([0,0,0-HEIGHT]) #18
+
+ faces.extend(Phillips_Fill(FaceStart_Outside,True))
+
+ Spin_Verts,Spin_Face = SpinDup(verts,faces,360,4,'z')
+
+ return Spin_Verts,Spin_Face,OUTTER_RADIUS * 2
+
+
+##########################################################################################
+##########################################################################################
+## Create Head Types
+##########################################################################################
+##########################################################################################
+
+def Max_Pan_Bit_Dia(HEAD_DIA):
+ HEAD_RADIUS = HEAD_DIA * 0.5
+ XRad = HEAD_RADIUS * 1.976
+ return (sin(radians(10))*XRad) * 2.0
+
+
+def Create_Pan_Head(HOLE_DIA,HEAD_DIA,SHANK_DIA,HEIGHT,RAD1,RAD2,FACE_OFFSET):
+
+ DIV = 36
+ HOLE_RADIUS = HOLE_DIA * 0.5
+ HEAD_RADIUS = HEAD_DIA * 0.5
+ SHANK_RADIUS = SHANK_DIA * 0.5
+
+ verts = []
+ faces = []
+ Row = 0
+ BEVEL = HEIGHT * 0.01
+ #Dome_Rad = HEAD_RADIUS * (1.0/1.75)
+
+ Dome_Rad = HEAD_RADIUS * 1.12
+ RAD_Offset = HEAD_RADIUS * 0.96
+ OtherRad = HEAD_RADIUS * 0.16
+ OtherRad_X_Offset = HEAD_RADIUS * 0.84
+ OtherRad_Z_Offset = HEAD_RADIUS * 0.504
+ XRad = HEAD_RADIUS * 1.976
+ ZRad = HEAD_RADIUS * 1.768
+ EndRad = HEAD_RADIUS * 0.284
+ EndZOffset = HEAD_RADIUS * 0.432
+ HEIGHT = HEAD_RADIUS * 0.59
+
+# Dome_Rad = 5.6
+# RAD_Offset = 4.9
+# OtherRad = 0.8
+# OtherRad_X_Offset = 4.2
+# OtherRad_Z_Offset = 2.52
+# XRad = 9.88
+# ZRad = 8.84
+# EndRad = 1.42
+# EndZOffset = 2.16
+# HEIGHT = 2.95
+
+ FaceStart = FACE_OFFSET
+
+ z = cos(radians(10))*ZRad
+ verts.append([HOLE_RADIUS,0.0,(0.0-ZRad)+z])
+ Start_Height = 0 - ((0.0-ZRad)+z)
+ Row += 1
+
+ #for i in range(0,30,10): was 0 to 30 more work needed to make this look good.
+ for i in range(10,30,10):
+ x = sin(radians(i))*XRad
+ z = cos(radians(i))*ZRad
+ verts.append([x,0.0,(0.0-ZRad)+z])
+ Row += 1
+
+ for i in range(20,140,10):
+ x = sin(radians(i))*EndRad
+ z = cos(radians(i))*EndRad
+ if ((0.0 - EndZOffset)+z) < (0.0-HEIGHT):
+ verts.append([(HEAD_RADIUS -EndRad)+x,0.0,0.0 - HEIGHT])
+ else:
+ verts.append([(HEAD_RADIUS -EndRad)+x,0.0,(0.0 - EndZOffset)+z])
+ Row += 1
+
+
+ verts.append([SHANK_RADIUS,0.0,(0.0-HEIGHT)])
+ Row += 1
+
+ verts.append([SHANK_RADIUS,0.0,(0.0-HEIGHT)-Start_Height])
+ Row += 1
+
+
+ sVerts,sFaces = SpinDup(verts,faces,360,DIV,'z')
+ sVerts.extend(verts) #add the start verts to the Spin verts to complete the loop
+
+ faces.extend(Build_Face_List_Quads(FaceStart,Row-1,DIV))
+
+ Global_Head_Height = HEIGHT ;
+
+
+ return Move_Verts_Up_Z(sVerts,Start_Height),faces,HEIGHT
+
+
+
+def Create_Dome_Head(HOLE_DIA,HEAD_DIA,SHANK_DIA,HEIGHT,RAD1,RAD2,FACE_OFFSET):
+ DIV = 36
+ HOLE_RADIUS = HOLE_DIA * 0.5
+ HEAD_RADIUS = HEAD_DIA * 0.5
+ SHANK_RADIUS = SHANK_DIA * 0.5
+
+ verts = []
+ faces = []
+ Row = 0
+ BEVEL = HEIGHT * 0.01
+ #Dome_Rad = HEAD_RADIUS * (1.0/1.75)
+
+ Dome_Rad = HEAD_RADIUS * 1.12
+ #Head_Height = HEAD_RADIUS * 0.78
+ RAD_Offset = HEAD_RADIUS * 0.98
+ Dome_Height = HEAD_RADIUS * 0.64
+ OtherRad = HEAD_RADIUS * 0.16
+ OtherRad_X_Offset = HEAD_RADIUS * 0.84
+ OtherRad_Z_Offset = HEAD_RADIUS * 0.504
+
+
+# Dome_Rad = 5.6
+# RAD_Offset = 4.9
+# Dome_Height = 3.2
+# OtherRad = 0.8
+# OtherRad_X_Offset = 4.2
+# OtherRad_Z_Offset = 2.52
+#
+
+ FaceStart = FACE_OFFSET
+
+ verts.append([HOLE_RADIUS,0.0,0.0])
+ Row += 1
+
+
+ for i in range(0,60,10):
+ x = sin(radians(i))*Dome_Rad
+ z = cos(radians(i))*Dome_Rad
+ if ((0.0-RAD_Offset)+z) <= 0:
+ verts.append([x,0.0,(0.0-RAD_Offset)+z])
+ Row += 1
+
+
+ for i in range(60,160,10):
+ x = sin(radians(i))*OtherRad
+ z = cos(radians(i))*OtherRad
+ z = (0.0-OtherRad_Z_Offset)+z
+ if z < (0.0-Dome_Height):
+ z = (0.0-Dome_Height)
+ verts.append([OtherRad_X_Offset+x,0.0,z])
+ Row += 1
+
+ verts.append([SHANK_RADIUS,0.0,(0.0-Dome_Height)])
+ Row += 1
+
+
+ sVerts,sFaces = SpinDup(verts,faces,360,DIV,'z')
+ sVerts.extend(verts) #add the start verts to the Spin verts to complete the loop
+
+ faces.extend(Build_Face_List_Quads(FaceStart,Row-1,DIV))
+
+ return sVerts,faces,Dome_Height
+
+
+
+def Create_CounterSink_Head(HOLE_DIA,HEAD_DIA,SHANK_DIA,HEIGHT,RAD1):
+ DIV = 36
+
+ HOLE_RADIUS = HOLE_DIA * 0.5
+ HEAD_RADIUS = HEAD_DIA * 0.5
+ SHANK_RADIUS = SHANK_DIA * 0.5
+
+
+ verts = []
+ faces = []
+ Row = 0
+ BEVEL = HEIGHT * 0.01
+
+
+
+# HEAD_RADIUS = (HEIGHT/tan(radians(60))) + SHANK_RADIUS
+ HEIGHT = tan(radians(60)) * (HEAD_RADIUS - SHANK_RADIUS)
+ #print (RAD1)
+
+ FaceStart = len(verts)
+
+ verts.append([HOLE_RADIUS,0.0,0.0])
+ Row += 1
+
+ #rad
+
+ for i in range(0,100,10):
+ x = sin(radians(i))*RAD1
+ z = cos(radians(i))*RAD1
+ verts.append([(HEAD_RADIUS-RAD1)+x,0.0,(0.0-RAD1)+z])
+ Row += 1
+
+
+ verts.append([SHANK_RADIUS,0.0,0.0-HEIGHT])
+ Row += 1
+
+
+ sVerts,sFaces = SpinDup(verts,faces,360,DIV,'z')
+ sVerts.extend(verts) #add the start verts to the Spin verts to complete the loop
+
+
+ faces.extend(Build_Face_List_Quads(FaceStart,Row-1,DIV,1))
+
+ return sVerts,faces,HEIGHT
+
+
+
+
+def Create_Cap_Head(HOLE_DIA,HEAD_DIA,SHANK_DIA,HEIGHT,RAD1,RAD2):
+ DIV = 36
+
+ HOLE_RADIUS = HOLE_DIA * 0.5
+ HEAD_RADIUS = HEAD_DIA * 0.5
+ SHANK_RADIUS = SHANK_DIA * 0.5
+
+ verts = []
+ faces = []
+ Row = 0
+ BEVEL = HEIGHT * 0.01
+
+
+ FaceStart = len(verts)
+
+ verts.append([HOLE_RADIUS,0.0,0.0])
+ Row += 1
+
+ #rad
+
+ for i in range(0,100,10):
+ x = sin(radians(i))*RAD1
+ z = cos(radians(i))*RAD1
+ verts.append([(HEAD_RADIUS-RAD1)+x,0.0,(0.0-RAD1)+z])
+ Row += 1
+
+
+ verts.append([HEAD_RADIUS,0.0,0.0-HEIGHT+BEVEL])
+ Row += 1
+
+ verts.append([HEAD_RADIUS-BEVEL,0.0,0.0-HEIGHT])
+ Row += 1
+
+ #rad2
+
+ for i in range(0,100,10):
+ x = sin(radians(i))*RAD2
+ z = cos(radians(i))*RAD2
+ verts.append([(SHANK_RADIUS+RAD2)-x,0.0,(0.0-HEIGHT-RAD2)+z])
+ Row += 1
+
+
+ sVerts,sFaces = SpinDup(verts,faces,360,DIV,'z')
+ sVerts.extend(verts) #add the start verts to the Spin verts to complete the loop
+
+
+ faces.extend(Build_Face_List_Quads(FaceStart,Row-1,DIV))
+
+ return sVerts,faces,HEIGHT+RAD2
+
+
+def Create_Hex_Head(FLAT,HOLE_DIA,SHANK_DIA,HEIGHT):
+
+ verts = []
+ faces = []
+ HOLE_RADIUS = HOLE_DIA * 0.5
+ Half_Flat = FLAT/2
+ TopBevelRadius = Half_Flat - (Half_Flat* (0.05/8))
+ Undercut_Height = (Half_Flat* (0.05/8))
+ Shank_Bevel = (Half_Flat* (0.05/8))
+ Flat_Height = HEIGHT - Undercut_Height - Shank_Bevel
+ #Undercut_Height = 5
+ SHANK_RADIUS = SHANK_DIA/2
+ Row = 0;
+
+ verts.append([0.0,0.0,0.0])
+
+
+ FaceStart = len(verts)
+ #inner hole
+
+ x = sin(radians(0))*HOLE_RADIUS
+ y = cos(radians(0))*HOLE_RADIUS
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/6))*HOLE_RADIUS
+ y = cos(radians(60/6))*HOLE_RADIUS
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/3))*HOLE_RADIUS
+ y = cos(radians(60/3))*HOLE_RADIUS
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/2))*HOLE_RADIUS
+ y = cos(radians(60/2))*HOLE_RADIUS
+ verts.append([x,y,0.0])
+ Row += 1
+
+ #bevel
+
+ x = sin(radians(0))*TopBevelRadius
+ y = cos(radians(0))*TopBevelRadius
+ vec1 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/6))*TopBevelRadius
+ y = cos(radians(60/6))*TopBevelRadius
+ vec2 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/3))*TopBevelRadius
+ y = cos(radians(60/3))*TopBevelRadius
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/2))*TopBevelRadius
+ y = cos(radians(60/2))*TopBevelRadius
+ vec4 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,0.0])
+ Row += 1
+
+ #Flats
+
+ x = tan(radians(0))*Half_Flat
+ dvec = vec1 - MATHUTILS.Vector([x,Half_Flat,0.0])
+ verts.append([x,Half_Flat,-dvec.length])
+
+
+ x = tan(radians(60/6))*Half_Flat
+ dvec = vec2 - MATHUTILS.Vector([x,Half_Flat,0.0])
+ verts.append([x,Half_Flat,-dvec.length])
+
+
+ x = tan(radians(60/3))*Half_Flat
+ dvec = vec3 - MATHUTILS.Vector([x,Half_Flat,0.0])
+ Lowest_Point = -dvec.length
+ verts.append([x,Half_Flat,-dvec.length])
+
+
+ x = tan(radians(60/2))*Half_Flat
+ dvec = vec4 - MATHUTILS.Vector([x,Half_Flat,0.0])
+ Lowest_Point = -dvec.length
+ verts.append([x,Half_Flat,-dvec.length])
+ Row += 1
+
+ #down Bits Tri
+ x = tan(radians(0))*Half_Flat
+ verts.append([x,Half_Flat,Lowest_Point])
+
+ x = tan(radians(60/6))*Half_Flat
+ verts.append([x,Half_Flat,Lowest_Point])
+
+ x = tan(radians(60/3))*Half_Flat
+ verts.append([x,Half_Flat,Lowest_Point])
+
+ x = tan(radians(60/2))*Half_Flat
+ verts.append([x,Half_Flat,Lowest_Point])
+ Row += 1
+
+ #down Bits
+
+ x = tan(radians(0))*Half_Flat
+ verts.append([x,Half_Flat,-Flat_Height])
+
+ x = tan(radians(60/6))*Half_Flat
+ verts.append([x,Half_Flat,-Flat_Height])
+
+ x = tan(radians(60/3))*Half_Flat
+ verts.append([x,Half_Flat,-Flat_Height])
+
+ x = tan(radians(60/2))*Half_Flat
+ verts.append([x,Half_Flat,-Flat_Height])
+ Row += 1
+
+
+ #under cut
+
+ x = sin(radians(0))*Half_Flat
+ y = cos(radians(0))*Half_Flat
+ vec1 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height])
+
+ x = sin(radians(60/6))*Half_Flat
+ y = cos(radians(60/6))*Half_Flat
+ vec2 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height])
+
+ x = sin(radians(60/3))*Half_Flat
+ y = cos(radians(60/3))*Half_Flat
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height])
+
+ x = sin(radians(60/2))*Half_Flat
+ y = cos(radians(60/2))*Half_Flat
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height])
+ Row += 1
+
+ #under cut down bit
+ x = sin(radians(0))*Half_Flat
+ y = cos(radians(0))*Half_Flat
+ vec1 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height])
+
+ x = sin(radians(60/6))*Half_Flat
+ y = cos(radians(60/6))*Half_Flat
+ vec2 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height])
+
+ x = sin(radians(60/3))*Half_Flat
+ y = cos(radians(60/3))*Half_Flat
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height])
+
+ x = sin(radians(60/2))*Half_Flat
+ y = cos(radians(60/2))*Half_Flat
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height])
+ Row += 1
+
+ #under cut to Shank BEVEAL
+ x = sin(radians(0))*(SHANK_RADIUS+Shank_Bevel)
+ y = cos(radians(0))*(SHANK_RADIUS+Shank_Bevel)
+ vec1 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height])
+
+ x = sin(radians(60/6))*(SHANK_RADIUS+Shank_Bevel)
+ y = cos(radians(60/6))*(SHANK_RADIUS+Shank_Bevel)
+ vec2 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height])
+
+ x = sin(radians(60/3))*(SHANK_RADIUS+Shank_Bevel)
+ y = cos(radians(60/3))*(SHANK_RADIUS+Shank_Bevel)
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height])
+
+ x = sin(radians(60/2))*(SHANK_RADIUS+Shank_Bevel)
+ y = cos(radians(60/2))*(SHANK_RADIUS+Shank_Bevel)
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height])
+ Row += 1
+
+ #under cut to Shank BEVEAL
+ x = sin(radians(0))*SHANK_RADIUS
+ y = cos(radians(0))*SHANK_RADIUS
+ vec1 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height-Shank_Bevel])
+
+ x = sin(radians(60/6))*SHANK_RADIUS
+ y = cos(radians(60/6))*SHANK_RADIUS
+ vec2 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height-Shank_Bevel])
+
+ x = sin(radians(60/3))*SHANK_RADIUS
+ y = cos(radians(60/3))*SHANK_RADIUS
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height-Shank_Bevel])
+
+ x = sin(radians(60/2))*SHANK_RADIUS
+ y = cos(radians(60/2))*SHANK_RADIUS
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,-Flat_Height-Undercut_Height-Shank_Bevel])
+ Row += 1
+
+
+ #Global_Head_Height = 0 - (-HEIGHT-0.1)
+ faces.extend(Build_Face_List_Quads(FaceStart,3,Row - 1))
+
+
+ Mirror_Verts,Mirror_Faces = Mirror_Verts_Faces(verts,faces,'y')
+ verts.extend(Mirror_Verts)
+ faces.extend(Mirror_Faces)
+
+ Spin_Verts,Spin_Faces = SpinDup(verts,faces,360,6,'z')
+
+
+ return Spin_Verts,Spin_Faces,0 - (-HEIGHT)
+
+
+##########################################################################################
+##########################################################################################
+## Create External Thread
+##########################################################################################
+##########################################################################################
+
+
+
+def Thread_Start3(verts,INNER_RADIUS,OUTTER_RADIUS,PITCH,DIV,CREST_PERCENT,ROOT_PERCENT,Height_Offset):
+
+
+ Ret_Row = 0;
+
+ Half_Pitch = float(PITCH)/2
+ Height_Start = Height_Offset - PITCH
+ Height_Step = float(PITCH)/float(DIV)
+ Deg_Step = 360.0 /float(DIV)
+
+ Crest_Height = float(PITCH) * float(CREST_PERCENT)/float(100)
+ Root_Height = float(PITCH) * float(ROOT_PERCENT)/float(100)
+ Root_to_Crest_Height = Crest_to_Root_Height = (float(PITCH) - (Crest_Height + Root_Height))/2.0
+
+#theard start
+
+ Rank = float(OUTTER_RADIUS - INNER_RADIUS)/float(DIV)
+ for j in range(4):
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,z])
+ Height_Offset -= Crest_Height
+ Ret_Row += 1
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,z ])
+ Height_Offset -= Crest_to_Root_Height
+ Ret_Row += 1
+
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+ if j == 0:
+ x = sin(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ y = cos(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ verts.append([x,y,z ])
+ Height_Offset -= Root_Height
+ Ret_Row += 1
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+
+ if j == 0:
+ x = sin(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ y = cos(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ verts.append([x,y,z ])
+ Height_Offset -= Root_to_Crest_Height
+ Ret_Row += 1
+
+ return Ret_Row,Height_Offset
+
+
+def Create_Shank_Verts(START_DIA,OUTTER_DIA,LENGTH,Z_LOCATION = 0):
+
+ verts = []
+ DIV = 36
+
+ START_RADIUS = START_DIA/2
+ OUTTER_RADIUS = OUTTER_DIA/2
+
+ Opp = abs(START_RADIUS - OUTTER_RADIUS)
+ Taper_Lentgh = Opp/tan(radians(31));
+
+ if Taper_Lentgh > LENGTH:
+ Taper_Lentgh = 0
+
+ Stright_Length = LENGTH - Taper_Lentgh
+
+ Deg_Step = 360.0 /float(DIV)
+
+ Row = 0
+
+ Lowest_Z_Vert = 0;
+
+ Height_Offset = Z_LOCATION
+
+
+ #ring
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*START_RADIUS
+ y = cos(radians(i*Deg_Step))*START_RADIUS
+ z = Height_Offset - 0
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Stright_Length
+ Row += 1
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*START_RADIUS
+ y = cos(radians(i*Deg_Step))*START_RADIUS
+ z = Height_Offset - 0
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Taper_Lentgh
+ Row += 1
+
+
+ return verts,Row,Height_Offset
+
+
+def Create_Thread_Start_Verts(INNER_DIA,OUTTER_DIA,PITCH,CREST_PERCENT,ROOT_PERCENT,Z_LOCATION = 0):
+
+ verts = []
+ DIV = 36
+
+ INNER_RADIUS = INNER_DIA/2
+ OUTTER_RADIUS = OUTTER_DIA/2
+
+ Half_Pitch = float(PITCH)/2
+ Deg_Step = 360.0 /float(DIV)
+ Height_Step = float(PITCH)/float(DIV)
+
+ Row = 0
+
+ Lowest_Z_Vert = 0;
+
+ Height_Offset = Z_LOCATION
+
+ Height_Start = Height_Offset
+
+ Crest_Height = float(PITCH) * float(CREST_PERCENT)/float(100)
+ Root_Height = float(PITCH) * float(ROOT_PERCENT)/float(100)
+ Root_to_Crest_Height = Crest_to_Root_Height = (float(PITCH) - (Crest_Height + Root_Height))/2.0
+
+ Rank = float(OUTTER_RADIUS - INNER_RADIUS)/float(DIV)
+
+ Height_Offset = Z_LOCATION + PITCH
+ Cut_off = Z_LOCATION
+
+
+ for j in range(1):
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ z = Height_Offset - (Height_Step*i)
+ if z > Cut_off : z = Cut_off
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Crest_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ z = Height_Offset - (Height_Step*i)
+ if z > Cut_off : z = Cut_off
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Crest_to_Root_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ z = Height_Offset - (Height_Step*i)
+ if z > Cut_off : z = Cut_off
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Root_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ z = Height_Offset - (Height_Step*i)
+ if z > Cut_off : z = Cut_off
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Root_to_Crest_Height
+ Row += 1
+
+
+ for j in range(2):
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Crest_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,z ])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Crest_to_Root_Height
+ Row += 1
+
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+ if j == 0:
+ x = sin(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ y = cos(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ verts.append([x,y,z ])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Root_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+
+ if j == 0:
+ x = sin(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ y = cos(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ verts.append([x,y,z ])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Root_to_Crest_Height
+ Row += 1
+
+
+ return verts,Row,Height_Offset
+
+
+
+def Create_Thread_Verts(INNER_DIA,OUTTER_DIA,PITCH,HEIGHT,CREST_PERCENT,ROOT_PERCENT,Z_LOCATION = 0):
+ verts = []
+
+ DIV = 36
+
+ INNER_RADIUS = INNER_DIA/2
+ OUTTER_RADIUS = OUTTER_DIA/2
+
+ Half_Pitch = float(PITCH)/2
+ Deg_Step = 360.0 /float(DIV)
+ Height_Step = float(PITCH)/float(DIV)
+
+ NUM_OF_START_THREADS = 4.0
+ NUM_OF_END_THREADS = 3.0
+ Num = int((HEIGHT- ((NUM_OF_START_THREADS*PITCH) + (NUM_OF_END_THREADS*PITCH) ))/PITCH)
+ Row = 0
+
+
+ Crest_Height = float(PITCH) * float(CREST_PERCENT)/float(100)
+ Root_Height = float(PITCH) * float(ROOT_PERCENT)/float(100)
+ Root_to_Crest_Height = Crest_to_Root_Height = (float(PITCH) - (Crest_Height + Root_Height))/2.0
+
+
+ Height_Offset = Z_LOCATION
+
+ Lowest_Z_Vert = 0;
+ FaceStart = len(verts)
+
+
+ for j in range(Num):
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ z = Height_Offset - (Height_Step*i)
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Crest_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ z = Height_Offset - (Height_Step*i)
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Crest_to_Root_Height
+ Row += 1
+
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+ z = Height_Offset - (Height_Step*i)
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Root_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+ z = Height_Offset - (Height_Step*i)
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Root_to_Crest_Height
+ Row += 1
+
+ return verts,Row,Height_Offset
+
+
+
+def Create_Thread_End_Verts(INNER_DIA,OUTTER_DIA,PITCH,CREST_PERCENT,ROOT_PERCENT,Z_LOCATION = 0):
+ verts = []
+
+ DIV = 36
+
+ INNER_RADIUS = INNER_DIA/2
+ OUTTER_RADIUS = OUTTER_DIA/2
+
+ Half_Pitch = float(PITCH)/2
+ Deg_Step = 360.0 /float(DIV)
+ Height_Step = float(PITCH)/float(DIV)
+
+ Crest_Height = float(PITCH) * float(CREST_PERCENT)/float(100)
+ Root_Height = float(PITCH) * float(ROOT_PERCENT)/float(100)
+ Root_to_Crest_Height = Crest_to_Root_Height = (float(PITCH) - (Crest_Height + Root_Height))/2.0
+
+ Col = 0
+ Row = 0
+
+ Height_Offset = Z_LOCATION
+
+ Tapper_Height_Start = Height_Offset - PITCH - PITCH
+
+ Max_Height = Tapper_Height_Start - PITCH
+
+ Lowest_Z_Vert = 0;
+
+ FaceStart = len(verts)
+ for j in range(4):
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ z = max(z,Max_Height)
+ Tapper_Radius = OUTTER_RADIUS
+ if z < Tapper_Height_Start:
+ Tapper_Radius = OUTTER_RADIUS - (Tapper_Height_Start - z)
+
+ x = sin(radians(i*Deg_Step))*(Tapper_Radius)
+ y = cos(radians(i*Deg_Step))*(Tapper_Radius)
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Crest_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ z = max(z,Max_Height)
+ Tapper_Radius = OUTTER_RADIUS
+ if z < Tapper_Height_Start:
+ Tapper_Radius = OUTTER_RADIUS - (Tapper_Height_Start - z)
+
+ x = sin(radians(i*Deg_Step))*(Tapper_Radius)
+ y = cos(radians(i*Deg_Step))*(Tapper_Radius)
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Crest_to_Root_Height
+ Row += 1
+
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ z = max(z,Max_Height)
+ Tapper_Radius = OUTTER_RADIUS - (Tapper_Height_Start - z)
+ if Tapper_Radius > INNER_RADIUS:
+ Tapper_Radius = INNER_RADIUS
+
+ x = sin(radians(i*Deg_Step))*(Tapper_Radius)
+ y = cos(radians(i*Deg_Step))*(Tapper_Radius)
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Root_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ z = max(z,Max_Height)
+ Tapper_Radius = OUTTER_RADIUS - (Tapper_Height_Start - z)
+ if Tapper_Radius > INNER_RADIUS:
+ Tapper_Radius = INNER_RADIUS
+
+ x = sin(radians(i*Deg_Step))*(Tapper_Radius)
+ y = cos(radians(i*Deg_Step))*(Tapper_Radius)
+ verts.append([x,y,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Height_Offset -= Root_to_Crest_Height
+ Row += 1
+
+ return verts,Row,Height_Offset,Lowest_Z_Vert
+
+
+
+
+def Create_External_Thread(SHANK_DIA,SHANK_LENGTH,INNER_DIA,OUTTER_DIA,PITCH,LENGTH,CREST_PERCENT,ROOT_PERCENT):
+
+ verts = []
+ faces = []
+
+ DIV = 36
+
+ Total_Row = 0
+ Thread_Len = 0;
+
+ Face_Start = len(verts)
+ Offset = 0.0;
+
+
+ Shank_Verts,Shank_Row,Offset = Create_Shank_Verts(SHANK_DIA,OUTTER_DIA,SHANK_LENGTH,Offset)
+ Total_Row += Shank_Row
+
+ Thread_Start_Verts,Thread_Start_Row,Offset = Create_Thread_Start_Verts(INNER_DIA,OUTTER_DIA,PITCH,CREST_PERCENT,ROOT_PERCENT,Offset)
+ Total_Row += Thread_Start_Row
+
+
+ Thread_Verts,Thread_Row,Offset = Create_Thread_Verts(INNER_DIA,OUTTER_DIA,PITCH,LENGTH,CREST_PERCENT,ROOT_PERCENT,Offset)
+ Total_Row += Thread_Row
+
+
+ Thread_End_Verts,Thread_End_Row,Offset,Lowest_Z_Vert = Create_Thread_End_Verts(INNER_DIA,OUTTER_DIA,PITCH,CREST_PERCENT,ROOT_PERCENT,Offset )
+ Total_Row += Thread_End_Row
+
+
+ verts.extend(Shank_Verts)
+ verts.extend(Thread_Start_Verts)
+ verts.extend(Thread_Verts)
+ verts.extend(Thread_End_Verts)
+
+ faces.extend(Build_Face_List_Quads(Face_Start,DIV,Total_Row -1,0))
+ faces.extend(Fill_Ring_Face(len(verts)-DIV,DIV,1))
+
+ return verts,faces,0.0 - Lowest_Z_Vert
+
+
+##########################################################################################
+##########################################################################################
+## Create Nut
+##########################################################################################
+##########################################################################################
+
+def add_Hex_Nut(FLAT,HOLE_DIA,HEIGHT):
+ global Global_Head_Height
+ global Global_NutRad
+
+ verts = []
+ faces = []
+ HOLE_RADIUS = HOLE_DIA * 0.5
+ Half_Flat = FLAT/2
+ Half_Height = HEIGHT/2
+ TopBevelRadius = Half_Flat - 0.05
+
+ Global_NutRad = TopBevelRadius
+
+ Row = 0;
+ Lowest_Z_Vert = 0.0;
+
+ verts.append([0.0,0.0,0.0])
+
+
+ FaceStart = len(verts)
+ #inner hole
+
+ x = sin(radians(0))*HOLE_RADIUS
+ y = cos(radians(0))*HOLE_RADIUS
+ #print ("rad 0 x;", x, "y:" ,y )
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/6))*HOLE_RADIUS
+ y = cos(radians(60/6))*HOLE_RADIUS
+ #print ("rad 60/6x;", x, "y:" ,y )
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/3))*HOLE_RADIUS
+ y = cos(radians(60/3))*HOLE_RADIUS
+ #print ("rad 60/3x;", x, "y:" ,y )
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/2))*HOLE_RADIUS
+ y = cos(radians(60/2))*HOLE_RADIUS
+ #print ("rad 60/2x;", x, "y:" ,y )
+ verts.append([x,y,0.0])
+ Row += 1
+
+
+ #bevel
+
+ x = sin(radians(0))*TopBevelRadius
+ y = cos(radians(0))*TopBevelRadius
+ vec1 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/6))*TopBevelRadius
+ y = cos(radians(60/6))*TopBevelRadius
+ vec2 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/3))*TopBevelRadius
+ y = cos(radians(60/3))*TopBevelRadius
+ vec3 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,0.0])
+
+
+ x = sin(radians(60/2))*TopBevelRadius
+ y = cos(radians(60/2))*TopBevelRadius
+ vec4 = MATHUTILS.Vector([x,y,0.0])
+ verts.append([x,y,0.0])
+ Row += 1
+
+ #Flats
+
+ x = tan(radians(0))*Half_Flat
+ dvec = vec1 - MATHUTILS.Vector([x,Half_Flat,0.0])
+ verts.append([x,Half_Flat,-dvec.length])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,-dvec.length)
+
+
+ x = tan(radians(60/6))*Half_Flat
+ dvec = vec2 - MATHUTILS.Vector([x,Half_Flat,0.0])
+ verts.append([x,Half_Flat,-dvec.length])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,-dvec.length)
+
+
+ x = tan(radians(60/3))*Half_Flat
+ dvec = vec3 - MATHUTILS.Vector([x,Half_Flat,0.0])
+ Lowest_Point = -dvec.length
+ verts.append([x,Half_Flat,-dvec.length])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,-dvec.length)
+
+ x = tan(radians(60/2))*Half_Flat
+ dvec = vec4 - MATHUTILS.Vector([x,Half_Flat,0.0])
+ Lowest_Point = -dvec.length
+ verts.append([x,Half_Flat,-dvec.length])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,-dvec.length)
+ Row += 1
+
+ #down Bits Tri
+ x = tan(radians(0))*Half_Flat
+ verts.append([x,Half_Flat,Lowest_Point])
+
+
+ x = tan(radians(60/6))*Half_Flat
+ verts.append([x,Half_Flat,Lowest_Point])
+ x = tan(radians(60/3))*Half_Flat
+ verts.append([x,Half_Flat,Lowest_Point])
+
+ x = tan(radians(60/2))*Half_Flat
+ verts.append([x,Half_Flat,Lowest_Point])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,Lowest_Point)
+ Row += 1
+
+ #down Bits
+
+ x = tan(radians(0))*Half_Flat
+ verts.append([x,Half_Flat,-Half_Height])
+
+ x = tan(radians(60/6))*Half_Flat
+ verts.append([x,Half_Flat,-Half_Height])
+
+ x = tan(radians(60/3))*Half_Flat
+ verts.append([x,Half_Flat,-Half_Height])
+
+ x = tan(radians(60/2))*Half_Flat
+ verts.append([x,Half_Flat,-Half_Height])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,-Half_Height)
+ Row += 1
+
+ faces.extend(Build_Face_List_Quads(FaceStart,3,Row - 1))
+
+ Global_Head_Height = HEIGHT
+
+ Tvert,tface = Mirror_Verts_Faces(verts,faces,'z',Lowest_Z_Vert)
+ verts.extend(Tvert)
+ faces.extend(tface)
+
+
+ Tvert,tface = Mirror_Verts_Faces(verts,faces,'y')
+ verts.extend(Tvert)
+ faces.extend(tface)
+
+ S_verts,S_faces = SpinDup(verts,faces,360,6,'z')
+
+ #return verts,faces,TopBevelRadius
+ return S_verts,S_faces,TopBevelRadius
+
+
+
+def add_Nylon_Head(OUTSIDE_RADIUS,Z_LOCATION = 0):
+ DIV = 36
+ verts = []
+ faces = []
+ Row = 0
+
+ INNER_HOLE = OUTSIDE_RADIUS - (OUTSIDE_RADIUS * (1.25/4.75))
+ EDGE_THICKNESS = (OUTSIDE_RADIUS * (0.4/4.75))
+ RAD1 = (OUTSIDE_RADIUS * (0.5/4.75))
+ OVER_ALL_HEIGTH = (OUTSIDE_RADIUS * (2.0/4.75))
+
+
+ FaceStart = len(verts)
+
+ Start_Height = 0 - 3
+ Height_Offset = Z_LOCATION
+ Lowest_Z_Vert = 0
+
+ x = INNER_HOLE
+ z = (Height_Offset - OVER_ALL_HEIGTH) + EDGE_THICKNESS
+ verts.append([x,0.0,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Row += 1
+
+ x = INNER_HOLE
+ z = (Height_Offset - OVER_ALL_HEIGTH)
+ verts.append([x,0.0,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Row += 1
+
+
+ for i in range(180,80,-10):
+ x = sin(radians(i))*RAD1
+ z = cos(radians(i))*RAD1
+ verts.append([(OUTSIDE_RADIUS-RAD1)+x,0.0,((Height_Offset - OVER_ALL_HEIGTH)+RAD1)+z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Row += 1
+
+
+ x = OUTSIDE_RADIUS - 0
+ z = Height_Offset
+ verts.append([x,0.0,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Row += 1
+
+ sVerts,sFaces = SpinDup(verts,faces,360,DIV,'z')
+ sVerts.extend(verts) #add the start verts to the Spin verts to complete the loop
+
+ faces.extend(Build_Face_List_Quads(FaceStart,Row-1,DIV))
+
+ return Move_Verts_Up_Z(sVerts,0),faces,Lowest_Z_Vert
+
+
+
+def add_Nylon_Part(OUTSIDE_RADIUS,Z_LOCATION = 0):
+ DIV = 36
+ verts = []
+ faces = []
+ Row = 0
+
+ INNER_HOLE = OUTSIDE_RADIUS - (OUTSIDE_RADIUS * (1.5/4.75))
+ EDGE_THICKNESS = (OUTSIDE_RADIUS * (0.4/4.75))
+ RAD1 = (OUTSIDE_RADIUS * (0.5/4.75))
+ OVER_ALL_HEIGTH = (OUTSIDE_RADIUS * (2.0/4.75))
+ PART_THICKNESS = OVER_ALL_HEIGTH - EDGE_THICKNESS
+ PART_INNER_HOLE = (OUTSIDE_RADIUS * (2.5/4.75))
+
+ FaceStart = len(verts)
+
+ Start_Height = 0 - 3
+ Height_Offset = Z_LOCATION
+ Lowest_Z_Vert = 0
+
+
+ x = INNER_HOLE + EDGE_THICKNESS
+ z = Height_Offset
+ verts.append([x,0.0,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Row += 1
+
+ x = PART_INNER_HOLE
+ z = Height_Offset
+ verts.append([x,0.0,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Row += 1
+
+ x = PART_INNER_HOLE
+ z = Height_Offset - PART_THICKNESS
+ verts.append([x,0.0,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Row += 1
+
+ x = INNER_HOLE + EDGE_THICKNESS
+ z = Height_Offset - PART_THICKNESS
+ verts.append([x,0.0,z])
+ Lowest_Z_Vert = min(Lowest_Z_Vert,z)
+ Row += 1
+
+
+ sVerts,sFaces = SpinDup(verts,faces,360,DIV,'z')
+ sVerts.extend(verts) #add the start verts to the Spin verts to complete the loop
+
+ faces.extend(Build_Face_List_Quads(FaceStart,Row-1,DIV,1))
+
+ return sVerts,faces,0 - Lowest_Z_Vert
+
+
+##########################################################################################
+##########################################################################################
+## Create Internal Thread
+##########################################################################################
+##########################################################################################
+
+
+def Create_Internal_Thread_Start_Verts(verts,INNER_RADIUS,OUTTER_RADIUS,PITCH,DIV,CREST_PERCENT,ROOT_PERCENT,Height_Offset):
+
+
+ Ret_Row = 0;
+
+ Height_Offset = Height_Offset + PITCH #Move the offset up so that the verts start at
+ #at the correct place (Height_Start)
+
+ Half_Pitch = float(PITCH)/2
+ Height_Start = Height_Offset - PITCH
+ Height_Step = float(PITCH)/float(DIV)
+ Deg_Step = 360.0 /float(DIV)
+
+ Crest_Height = float(PITCH) * float(CREST_PERCENT)/float(100)
+ Root_Height = float(PITCH) * float(ROOT_PERCENT)/float(100)
+ Root_to_Crest_Height = Crest_to_Root_Height = (float(PITCH) - (Crest_Height + Root_Height))/2.0
+
+
+ Rank = float(OUTTER_RADIUS - INNER_RADIUS)/float(DIV)
+ for j in range(1):
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,z])
+ Height_Offset -= Crest_Height
+ Ret_Row += 1
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,z ])
+ Height_Offset -= Crest_to_Root_Height
+ Ret_Row += 1
+
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+ if j == 0:
+ x = sin(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ y = cos(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ verts.append([x,y,z ])
+ Height_Offset -= Root_Height
+ Ret_Row += 1
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z > Height_Start:
+ z = Height_Start
+
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+
+ if j == 0:
+ x = sin(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ y = cos(radians(i*Deg_Step))*(OUTTER_RADIUS - (i*Rank))
+ verts.append([x,y,z ])
+ Height_Offset -= Root_to_Crest_Height
+ Ret_Row += 1
+
+ return Ret_Row,Height_Offset
+
+
+def Create_Internal_Thread_End_Verts(verts,INNER_RADIUS,OUTTER_RADIUS,PITCH,DIV,CREST_PERCENT,ROOT_PERCENT,Height_Offset):
+
+
+ Ret_Row = 0;
+
+ Half_Pitch = float(PITCH)/2
+ #Height_End = Height_Offset - PITCH - PITCH - PITCH- PITCH - PITCH- PITCH
+ Height_End = Height_Offset - PITCH
+ #Height_End = -2.1
+ Height_Step = float(PITCH)/float(DIV)
+ Deg_Step = 360.0 /float(DIV)
+
+ Crest_Height = float(PITCH) * float(CREST_PERCENT)/float(100)
+ Root_Height = float(PITCH) * float(ROOT_PERCENT)/float(100)
+ Root_to_Crest_Height = Crest_to_Root_Height = (float(PITCH) - (Crest_Height + Root_Height))/2.0
+
+
+ Rank = float(OUTTER_RADIUS - INNER_RADIUS)/float(DIV)
+
+ Num = 0
+
+ for j in range(2):
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z < Height_End:
+ z = Height_End
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,z])
+ Height_Offset -= Crest_Height
+ Ret_Row += 1
+
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z < Height_End:
+ z = Height_End
+
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,z ])
+ Height_Offset -= Crest_to_Root_Height
+ Ret_Row += 1
+
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z < Height_End:
+ z = Height_End
+
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+ if j == Num:
+ x = sin(radians(i*Deg_Step))*(INNER_RADIUS + (i*Rank))
+ y = cos(radians(i*Deg_Step))*(INNER_RADIUS + (i*Rank))
+ if j > Num:
+ x = sin(radians(i*Deg_Step))*(OUTTER_RADIUS)
+ y = cos(radians(i*Deg_Step))*(OUTTER_RADIUS )
+
+ verts.append([x,y,z ])
+ Height_Offset -= Root_Height
+ Ret_Row += 1
+
+
+ for i in range(DIV+1):
+ z = Height_Offset - (Height_Step*i)
+ if z < Height_End:
+ z = Height_End
+
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+
+ if j == Num:
+ x = sin(radians(i*Deg_Step))*(INNER_RADIUS + (i*Rank))
+ y = cos(radians(i*Deg_Step))*(INNER_RADIUS + (i*Rank))
+ if j > Num:
+ x = sin(radians(i*Deg_Step))*(OUTTER_RADIUS )
+ y = cos(radians(i*Deg_Step))*(OUTTER_RADIUS )
+
+ verts.append([x,y,z ])
+ Height_Offset -= Root_to_Crest_Height
+ Ret_Row += 1
+
+
+ return Ret_Row,Height_End # send back Height End as this is the lowest point
+
+
+def Create_Internal_Thread(INNER_DIA,OUTTER_DIA,PITCH,HEIGHT,CREST_PERCENT,ROOT_PERCENT,INTERNAL = 1):
+ verts = []
+ faces = []
+
+ DIV = 36
+
+ INNER_RADIUS = INNER_DIA/2
+ OUTTER_RADIUS = OUTTER_DIA/2
+
+ Half_Pitch = float(PITCH)/2
+ Deg_Step = 360.0 /float(DIV)
+ Height_Step = float(PITCH)/float(DIV)
+
+ Num = int(round((HEIGHT- PITCH)/PITCH)) # less one pitch for the start and end that is 1/2 pitch high
+
+ Col = 0
+ Row = 0
+
+
+ Crest_Height = float(PITCH) * float(CREST_PERCENT)/float(100)
+ Root_Height = float(PITCH) * float(ROOT_PERCENT)/float(100)
+ Root_to_Crest_Height = Crest_to_Root_Height = (float(PITCH) - (Crest_Height + Root_Height))/2.0
+
+ Height_Offset = 0
+ FaceStart = len(verts)
+
+ Row_Inc,Height_Offset = Create_Internal_Thread_Start_Verts(verts,INNER_RADIUS,OUTTER_RADIUS,PITCH,DIV,CREST_PERCENT,ROOT_PERCENT,Height_Offset)
+ Row += Row_Inc
+
+ for j in range(Num):
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,Height_Offset - (Height_Step*i) ])
+ Height_Offset -= Crest_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*OUTTER_RADIUS
+ y = cos(radians(i*Deg_Step))*OUTTER_RADIUS
+ verts.append([x,y,Height_Offset - (Height_Step*i) ])
+ Height_Offset -= Crest_to_Root_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+ verts.append([x,y,Height_Offset - (Height_Step*i) ])
+ Height_Offset -= Root_Height
+ Row += 1
+
+ for i in range(DIV+1):
+ x = sin(radians(i*Deg_Step))*INNER_RADIUS
+ y = cos(radians(i*Deg_Step))*INNER_RADIUS
+ verts.append([x,y,Height_Offset - (Height_Step*i) ])
+ Height_Offset -= Root_to_Crest_Height
+ Row += 1
+
+
+ Row_Inc,Height_Offset = Create_Internal_Thread_End_Verts(verts,INNER_RADIUS,OUTTER_RADIUS,PITCH,DIV,CREST_PERCENT,ROOT_PERCENT,Height_Offset)
+ Row += Row_Inc
+
+ faces.extend(Build_Face_List_Quads(FaceStart,DIV,Row -1,INTERNAL))
+
+ return verts,faces,0 - Height_Offset
+
+
+def Nut_Mesh(props, context):
+
+ verts = []
+ faces = []
+ Head_Verts = []
+ Head_Faces= []
+ #sc = context.scene
+
+ New_Nut_Height = 5
+
+ Face_Start = len(verts)
+ Thread_Verts,Thread_Faces,New_Nut_Height = Create_Internal_Thread(props.bf_Minor_Dia,props.bf_Major_Dia,props.bf_Pitch,props.bf_Hex_Nut_Height,props.bf_Crest_Percent,props.bf_Root_Percent,1)
+ verts.extend(Thread_Verts)
+ faces.extend(Copy_Faces(Thread_Faces,Face_Start))
+
+ Face_Start = len(verts)
+ Head_Verts,Head_Faces,Lock_Nut_Rad = add_Hex_Nut(props.bf_Hex_Nut_Flat_Distance,props.bf_Major_Dia,New_Nut_Height)
+ verts.extend((Head_Verts))
+ faces.extend(Copy_Faces(Head_Faces,Face_Start))
+
+ LowZ = 0 - New_Nut_Height
+
+ if props.bf_Nut_Type == 'bf_Nut_Lock':
+ Face_Start = len(verts)
+ Nylon_Head_Verts,Nylon_Head_faces,LowZ = add_Nylon_Head(Lock_Nut_Rad,0-New_Nut_Height)
+ verts.extend((Nylon_Head_Verts))
+ faces.extend(Copy_Faces(Nylon_Head_faces,Face_Start))
+
+ Face_Start = len(verts)
+ Nylon_Verts,Nylon_faces,Temp_LowZ = add_Nylon_Part(Lock_Nut_Rad,0-New_Nut_Height)
+ verts.extend((Nylon_Verts))
+ faces.extend(Copy_Faces(Nylon_faces,Face_Start))
+
+
+ return Move_Verts_Up_Z(verts,0 - LowZ),faces
+
+
+
+##########################################################################################
+##########################################################################################
+##########################################################################################
+## Create Bolt
+##########################################################################################
+##########################################################################################
+
+
+
+def Bolt_Mesh(props, context):
+
+
+ verts = []
+ faces = []
+ Bit_Verts = []
+ Bit_Faces = []
+ Bit_Dia = 0.001
+ Head_Verts = []
+ Head_Faces= []
+ Head_Height = 0.0
+ #sc = context.scene
+
+ ReSized_Allen_Bit_Flat_Distance = props.bf_Allen_Bit_Flat_Distance # set default
+
+
+ Head_Height = props.bf_Hex_Head_Height # will be changed by the Head Functions
+
+
+ if props.bf_Bit_Type == 'bf_Bit_Allen' and props.bf_Head_Type == 'bf_Head_Pan':
+ #need to size Allen bit if it is too big.
+ if Allen_Bit_Dia(props.bf_Allen_Bit_Flat_Distance) > Max_Pan_Bit_Dia(props.bf_Pan_Head_Dia):
+ ReSized_Allen_Bit_Flat_Distance = Allen_Bit_Dia_To_Flat(Max_Pan_Bit_Dia(props.bf_Pan_Head_Dia)) * 1.05
+ #print ("Resized Allen Bit Flat Distance to ",ReSized_Allen_Bit_Flat_Distance)
+
+ #bit Mesh
+ if props.bf_Bit_Type == 'bf_Bit_Allen':
+ Bit_Verts,Bit_Faces,Bit_Dia = Create_Allen_Bit(ReSized_Allen_Bit_Flat_Distance,props.bf_Allen_Bit_Depth)
+
+ if props.bf_Bit_Type == 'bf_Bit_Philips':
+ Bit_Verts,Bit_Faces,Bit_Dia = Create_Phillips_Bit(props.bf_Philips_Bit_Dia,props.bf_Philips_Bit_Dia*(0.5/1.82),props.bf_Phillips_Bit_Depth)
+
+
+ #Head Mesh
+
+ if props.bf_Head_Type =='bf_Head_Hex':
+ Head_Verts,Head_Faces,Head_Height = Create_Hex_Head(props.bf_Hex_Head_Flat_Distance,Bit_Dia,props.bf_Shank_Dia,props.bf_Hex_Head_Height)
+
+ elif props.bf_Head_Type == 'bf_Head_Cap':
+ Head_Verts,Head_Faces,Head_Height = Create_Cap_Head(Bit_Dia,props.bf_Cap_Head_Dia,props.bf_Shank_Dia,props.bf_Cap_Head_Height,props.bf_Cap_Head_Dia*(1.0/19.0),props.bf_Cap_Head_Dia*(1.0/19.0))
+
+ elif props.bf_Head_Type =='bf_Head_Dome':
+ Head_Verts,Head_Faces,Head_Height = Create_Dome_Head(Bit_Dia,props.bf_Dome_Head_Dia,props.bf_Shank_Dia,props.bf_Hex_Head_Height,1,1,0)
+
+ elif props.bf_Head_Type == 'bf_Head_Pan':
+ Head_Verts,Head_Faces,Head_Height = Create_Pan_Head(Bit_Dia,props.bf_Pan_Head_Dia,props.bf_Shank_Dia,props.bf_Hex_Head_Height,1,1,0)
+
+ elif props.bf_Head_Type == 'bf_Head_CounterSink':
+ Head_Verts,Head_Faces,Head_Height = Create_CounterSink_Head(Bit_Dia,props.bf_CounterSink_Head_Dia,props.bf_Shank_Dia,props.bf_CounterSink_Head_Dia,props.bf_CounterSink_Head_Dia*(0.09/6.31))
+#Head_Verts,Head_Faces,Head_Height = Create_CounterSink_Head(Bit_Dia,props.bf_CounterSink_Head_Dia,props.bf_Shank_Dia,props.bf_CounterSink_Head_Dia,props.bf_CounterSink_Head_Dia*(1.0/19.0))
+
+ Face_Start = len(verts)
+ verts.extend(Move_Verts_Up_Z(Bit_Verts,Head_Height))
+ faces.extend(Copy_Faces(Bit_Faces,Face_Start))
+
+ Face_Start = len(verts)
+ verts.extend(Move_Verts_Up_Z(Head_Verts,Head_Height))
+ faces.extend(Copy_Faces(Head_Faces,Face_Start))
+
+ Face_Start = len(verts)
+ Thread_Verts,Thread_Faces,Thread_Height = Create_External_Thread(props.bf_Shank_Dia,props.bf_Shank_Length,props.bf_Minor_Dia,props.bf_Major_Dia,props.bf_Pitch,props.bf_Thread_Length,props.bf_Crest_Percent,props.bf_Root_Percent)
+
+ verts.extend(Move_Verts_Up_Z(Thread_Verts,00))
+ faces.extend(Copy_Faces(Thread_Faces,Face_Start))
+
+ return Move_Verts_Up_Z(verts,Thread_Height),faces
+
+# calculates the matrix for the new object
+# depending on user pref
+def align_matrix(context):
+ loc = Matrix.Translation(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
+
+
+def Create_New_Mesh(props, context, align_matrix):
+
+ verts = []
+ faces = []
+ sMeshName =''
+ sObjName =''
+
+ if props.bf_Model_Type == 'bf_Model_Bolt':
+ #print('Create Bolt')
+ verts, faces = Bolt_Mesh(props, context)
+ sMeshName = 'Bolt'
+ sObjName = 'Bolt'
+
+ if props.bf_Model_Type == 'bf_Model_Nut':
+ #print('Create Nut')
+ verts, faces = Nut_Mesh(props, context)
+ sMeshName = 'Nut'
+ sObjName = 'Nut'
+
+
+ verts, faces = RemoveDoubles(verts, faces)
+
+ verts = Scale_Mesh_Verts(verts,GLOBAL_SCALE)
+
+ obj = create_mesh_object(context, verts, [], faces,sObjName,
+ props.edit, align_matrix)
+
+
+ #print("Created_Object")
+ return
+
+
diff --git a/add_mesh_BoltFactory/preset_utils.py b/add_mesh_BoltFactory/preset_utils.py
new file mode 100644
index 00000000..4d31accf
--- /dev/null
+++ b/add_mesh_BoltFactory/preset_utils.py
@@ -0,0 +1,53 @@
+# ##### 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
+import os, sys
+
+
+def getPresets():
+
+ scriptPath = os.path.dirname(__file__)
+ presetPath = os.path.join(scriptPath, "presets")
+ presetFiles = os.listdir(presetPath)
+ #presetFiles.sort()
+
+ presets = [(presetFile, presetFile.rpartition(".")[0], presetFile)
+ for i, presetFile in enumerate(presetFiles) if presetFile.endswith(".py")]
+
+ #print(presets)
+ return presets, presetPath
+
+
+#presets = getPresets()
+
+
+
+def setProps(props, preset, presetsPath):
+
+ #bpy.ops.script.python_file_run(filepath=presetsPath + '\\' + preset)
+
+ file = open(os.path.join(presetsPath, preset))
+
+ for line in file:
+ exec(line)
+
+ file.close()
+
+ return
diff --git a/add_mesh_BoltFactory/presets/M10.py b/add_mesh_BoltFactory/presets/M10.py
new file mode 100644
index 00000000..f2ea9c8d
--- /dev/null
+++ b/add_mesh_BoltFactory/presets/M10.py
@@ -0,0 +1,22 @@
+props.bf_Shank_Dia = 10.0
+#props.bf_Pitch = 1.5 #Coarse
+props.bf_Pitch = 1.25 #Fine
+props.bf_Crest_Percent = 10
+props.bf_Root_Percent = 10
+props.bf_Major_Dia = 10.0
+props.bf_Minor_Dia = props.bf_Major_Dia - (1.082532 * props.bf_Pitch)
+props.bf_Hex_Head_Flat_Distance = 17.0
+props.bf_Hex_Head_Height = 6.4
+props.bf_Cap_Head_Dia = 16.0
+props.bf_Cap_Head_Height = 10.0
+props.bf_CounterSink_Head_Dia = 20.0
+props.bf_Allen_Bit_Flat_Distance = 8.0
+props.bf_Allen_Bit_Depth = 5.0
+props.bf_Pan_Head_Dia = 20.0
+props.bf_Dome_Head_Dia = 20.0
+props.bf_Philips_Bit_Dia = props.bf_Pan_Head_Dia*(1.82/5.6)
+#props.bf_Phillips_Bit_Depth = Get_Phillips_Bit_Height(props.bf_Philips_Bit_Dia)
+props.bf_Hex_Nut_Height = 8.0
+props.bf_Hex_Nut_Flat_Distance = 17.0
+props.bf_Thread_Length = 20
+props.bf_Shank_Length = 0.0
diff --git a/add_mesh_BoltFactory/presets/M12.py b/add_mesh_BoltFactory/presets/M12.py
new file mode 100644
index 00000000..44f3876f
--- /dev/null
+++ b/add_mesh_BoltFactory/presets/M12.py
@@ -0,0 +1,22 @@
+#props.bf_Pitch = 1.75 #Coarse
+props.bf_Pitch = 1.50 #Fine
+props.bf_Crest_Percent = 10
+props.bf_Root_Percent = 10
+props.bf_Major_Dia = 12.0
+props.bf_Minor_Dia = props.bf_Major_Dia - (1.082532 * props.bf_Pitch)
+props.bf_Hex_Head_Flat_Distance = 19.0
+props.bf_Hex_Head_Height = 7.5
+props.bf_Cap_Head_Dia = 18.5
+props.bf_Cap_Head_Height = 12.0
+props.bf_CounterSink_Head_Dia = 22.0
+props.bf_Allen_Bit_Flat_Distance = 10.0
+props.bf_Allen_Bit_Depth = 6.0
+props.bf_Pan_Head_Dia = 24.0
+props.bf_Dome_Head_Dia = 24.0
+props.bf_Philips_Bit_Dia = props.bf_Pan_Head_Dia*(1.82/5.6)
+#props.bf_Phillips_Bit_Depth = Get_Phillips_Bit_Height(props.bf_Philips_Bit_Dia)
+props.bf_Hex_Nut_Height = 10.0
+props.bf_Hex_Nut_Flat_Distance = 19.0
+props.bf_Shank_Dia = 12.0
+props.bf_Shank_Length = 33.0
+props.bf_Thread_Length = 32.0
diff --git a/add_mesh_BoltFactory/presets/M3.py b/add_mesh_BoltFactory/presets/M3.py
new file mode 100644
index 00000000..3e970468
--- /dev/null
+++ b/add_mesh_BoltFactory/presets/M3.py
@@ -0,0 +1,22 @@
+props.bf_Shank_Dia = 3.0
+#props.bf_Pitch = 0.5 #Coarse
+props.bf_Pitch = 0.35 #Fine
+props.bf_Crest_Percent = 10
+props.bf_Root_Percent = 10
+props.bf_Major_Dia = 3.0
+props.bf_Minor_Dia = props.bf_Major_Dia - (1.082532 * props.bf_Pitch)
+props.bf_Hex_Head_Flat_Distance = 5.5
+props.bf_Hex_Head_Height = 2.0
+props.bf_Cap_Head_Dia = 5.5
+props.bf_Cap_Head_Height = 3.0
+props.bf_CounterSink_Head_Dia = 6.3
+props.bf_Allen_Bit_Flat_Distance = 2.5
+props.bf_Allen_Bit_Depth = 1.5
+props.bf_Pan_Head_Dia = 5.6
+props.bf_Dome_Head_Dia = 5.6
+props.bf_Philips_Bit_Dia = props.bf_Pan_Head_Dia*(1.82/5.6)
+#props.bf_Phillips_Bit_Depth = Get_Phillips_Bit_Height(props.bf_Philips_Bit_Dia)
+props.bf_Hex_Nut_Height = 2.4
+props.bf_Hex_Nut_Flat_Distance = 5.5
+props.bf_Thread_Length = 6
+props.bf_Shank_Length = 0.0
diff --git a/add_mesh_BoltFactory/presets/M4.py b/add_mesh_BoltFactory/presets/M4.py
new file mode 100644
index 00000000..5682b442
--- /dev/null
+++ b/add_mesh_BoltFactory/presets/M4.py
@@ -0,0 +1,22 @@
+props.bf_Shank_Dia = 4.0
+#props.bf_Pitch = 0.7 #Coarse
+props.bf_Pitch = 0.5 #Fine
+props.bf_Crest_Percent = 10
+props.bf_Root_Percent = 10
+props.bf_Major_Dia = 4.0
+props.bf_Minor_Dia = props.bf_Major_Dia - (1.082532 * props.bf_Pitch)
+props.bf_Hex_Head_Flat_Distance = 7.0
+props.bf_Hex_Head_Height = 2.8
+props.bf_Cap_Head_Dia = 7.0
+props.bf_Cap_Head_Height = 4.0
+props.bf_CounterSink_Head_Dia = 9.4
+props.bf_Allen_Bit_Flat_Distance = 3.0
+props.bf_Allen_Bit_Depth = 2.0
+props.bf_Pan_Head_Dia = 8.0
+props.bf_Dome_Head_Dia = 8.0
+props.bf_Philips_Bit_Dia = props.bf_Pan_Head_Dia*(1.82/5.6)
+#props.bf_Phillips_Bit_Depth = Get_Phillips_Bit_Height(props.bf_Philips_Bit_Dia)
+props.bf_Hex_Nut_Height = 3.2
+props.bf_Hex_Nut_Flat_Distance = 7.0
+props.bf_Thread_Length = 8
+props.bf_Shank_Length = 0.0
diff --git a/add_mesh_BoltFactory/presets/M5.py b/add_mesh_BoltFactory/presets/M5.py
new file mode 100644
index 00000000..b889f349
--- /dev/null
+++ b/add_mesh_BoltFactory/presets/M5.py
@@ -0,0 +1,22 @@
+props.bf_Shank_Dia = 5.0
+#props.bf_Pitch = 0.8 #Coarse
+props.bf_Pitch = 0.5 #Fine
+props.bf_Crest_Percent = 10
+props.bf_Root_Percent = 10
+props.bf_Major_Dia = 5.0
+props.bf_Minor_Dia = props.bf_Major_Dia - (1.082532 * props.bf_Pitch)
+props.bf_Hex_Head_Flat_Distance = 8.0
+props.bf_Hex_Head_Height = 3.5
+props.bf_Cap_Head_Dia = 8.5
+props.bf_Cap_Head_Height = 5.0
+props.bf_CounterSink_Head_Dia = 10.4
+props.bf_Allen_Bit_Flat_Distance = 4.0
+props.bf_Allen_Bit_Depth = 2.5
+props.bf_Pan_Head_Dia = 9.5
+props.bf_Dome_Head_Dia = 9.5
+props.bf_Philips_Bit_Dia = props.bf_Pan_Head_Dia*(1.82/5.6)
+#props.bf_Phillips_Bit_Depth = Get_Phillips_Bit_Height(props.bf_Philips_Bit_Dia)
+props.bf_Hex_Nut_Height = 4.0
+props.bf_Hex_Nut_Flat_Distance = 8.0
+props.bf_Thread_Length = 10
+props.bf_Shank_Length = 0.0
diff --git a/add_mesh_BoltFactory/presets/M6.py b/add_mesh_BoltFactory/presets/M6.py
new file mode 100644
index 00000000..2b11a43d
--- /dev/null
+++ b/add_mesh_BoltFactory/presets/M6.py
@@ -0,0 +1,22 @@
+props.bf_Shank_Dia = 6.0
+#bf_Pitch = 1.0 #Coarse
+props.bf_Pitch = 0.75 #Fine
+props.bf_Crest_Percent = 10
+props.bf_Root_Percent = 10
+props.bf_Major_Dia = 6.0
+props.bf_Minor_Dia = props.bf_Major_Dia - (1.082532 * props.bf_Pitch)
+props.bf_Hex_Head_Flat_Distance = 10.0
+props.bf_Hex_Head_Height = 4.0
+props.bf_Cap_Head_Dia = 10.0
+props.bf_Cap_Head_Height = 6.0
+props.bf_CounterSink_Head_Dia = 12.6
+props.bf_Allen_Bit_Flat_Distance = 5.0
+props.bf_Allen_Bit_Depth = 3.0
+props.bf_Pan_Head_Dia = 12.0
+props.bf_Dome_Head_Dia = 12.0
+props.bf_Philips_Bit_Dia = props.bf_Pan_Head_Dia*(1.82/5.6)
+#props.bf_Phillips_Bit_Depth = Get_Phillips_Bit_Height(props.bf_Philips_Bit_Dia)
+props.bf_Hex_Nut_Height = 5.0
+props.bf_Hex_Nut_Flat_Distance = 10.0
+props.bf_Thread_Length = 12
+props.bf_Shank_Length = 0.0
diff --git a/add_mesh_BoltFactory/presets/M8.py b/add_mesh_BoltFactory/presets/M8.py
new file mode 100644
index 00000000..77cbb497
--- /dev/null
+++ b/add_mesh_BoltFactory/presets/M8.py
@@ -0,0 +1,22 @@
+props.bf_Shank_Dia = 8.0
+#props.bf_Pitch = 1.25 #Coarse
+props.bf_Pitch = 1.00 #Fine
+props.bf_Crest_Percent = 10
+props.bf_Root_Percent = 10
+props.bf_Major_Dia = 8.0
+props.bf_Minor_Dia = props.bf_Major_Dia - (1.082532 * props.bf_Pitch)
+props.bf_Hex_Head_Flat_Distance = 13.0
+props.bf_Hex_Head_Height = 5.3
+props.bf_Cap_Head_Dia = 13.5
+props.bf_Cap_Head_Height = 8.0
+props.bf_CounterSink_Head_Dia = 17.3
+props.bf_Allen_Bit_Flat_Distance = 6.0
+props.bf_Allen_Bit_Depth = 4.0
+props.bf_Pan_Head_Dia = 16.0
+props.bf_Dome_Head_Dia = 16.0
+props.bf_Philips_Bit_Dia = props.bf_Pan_Head_Dia*(1.82/5.6)
+#props.bf_Phillips_Bit_Depth = Get_Phillips_Bit_Height(props.bf_Philips_Bit_Dia)
+props.bf_Hex_Nut_Height = 6.5
+props.bf_Hex_Nut_Flat_Distance = 13.0
+props.bf_Thread_Length = 16
+props.bf_Shank_Length = 0.0
diff --git a/add_mesh_ant_landscape.py b/add_mesh_ant_landscape.py
new file mode 100644
index 00000000..44cbf775
--- /dev/null
+++ b/add_mesh_ant_landscape.py
@@ -0,0 +1,911 @@
+# ##### 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": "ANT Landscape",
+ "author": "Jimmy Hazevoet",
+ "version": (0,1,0),
+ "blender": (2, 5, 4),
+ "api": 32411,
+ "location": "Add Mesh menu",
+ "description": "Adds a landscape primitive",
+ "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/ANT_Landscape",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=23130&group_id=153&atid=469",
+ "category": "Add Mesh"}
+
+'''
+Another Noise Tool: Landscape mesh generator
+
+MESH OPTIONS:
+Mesh update: Turn this on for interactive mesh update.
+Sphere: Generate sphere or a grid mesh. (Turn height falloff off for sphere mesh)
+Smooth: Generate smooth shaded mesh.
+Subdivision: Number of mesh subdivisions, higher numbers gives more detail but also slows down the script.
+Mesh size: X,Y size of the grid mesh (in blender units).
+
+NOISE OPTIONS: ( Most of these options are the same as in blender textures. )
+Random seed: Use this to randomise the origin of the noise function.
+Noise size: Size of the noise.
+Noise type: Available noise types: multiFractal, ridgedMFractal, hybridMFractal, heteroTerrain, Turbulence, Distorted Noise, Cellnoise, Shattered_hTerrain, Marble
+Noise basis: Blender, Perlin, NewPerlin, Voronoi_F1, Voronoi_F2, Voronoi_F3, Voronoi_F4, Voronoi_F2-F1, Voronoi Crackle, Cellnoise
+VLNoise basis: Blender, Perlin, NewPerlin, Voronoi_F1, Voronoi_F2, Voronoi_F3, Voronoi_F4, Voronoi_F2-F1, Voronoi Crackle, Cellnoise
+Distortion: Distortion amount.
+Hard: Hard/Soft turbulence noise.
+Depth: Noise depth, number of frequencies in the fBm.
+Dimension: Musgrave: Fractal dimension of the roughest areas.
+Lacunarity: Musgrave: Gap between successive frequencies.
+Offset: Musgrave: Raises the terrain from sea level.
+Gain: Musgrave: Scale factor.
+Marble Bias: Sin, Tri, Saw
+Marble Sharpnes: Soft, Sharp, Sharper
+Marble Shape: Shape of the marble function: Default, Ring, Swirl, X, Y
+
+HEIGHT OPTIONS:
+Invert: Invert terrain height.
+Height: Scale terrain height.
+Offset: Terrain height offset.
+Falloff: Terrain height falloff: Type 1, Type 2, X, Y
+Sealevel: Flattens terrain below sealevel.
+Platlevel: Flattens terrain above plateau level.
+Strata: Strata amount, number of strata/terrace layers.
+Strata type: Strata types, Smooth, Sharp-sub, Sharp-add
+'''
+
+# import modules
+import bpy
+from bpy.props import *
+from mathutils import *
+from noise import *
+from math import *
+
+
+###------------------------------------------------------------
+# calculates the matrix for the new object depending on user pref
+def align_matrix(context):
+ loc = Matrix.Translation(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
+
+
+###------------------------------------------------------------
+###------------------------------------------------------------
+# some functions for marble_noise
+def sin_bias(a):
+ return 0.5 + 0.5 * sin(a)
+
+def tri_bias(a):
+ b = 2 * pi
+ a = 1 - 2 * abs(floor((a * (1/b))+0.5) - (a*(1/b)))
+ return a
+
+def saw_bias(a):
+ b = 2 * pi
+ n = int(a/b)
+ a -= n * b
+ if a < 0: a += b
+ return a / b
+
+def soft(a):
+ return a
+
+def sharp(a):
+ return a**0.5
+
+def sharper(a):
+ return sharp(sharp(a))
+
+def shapes(x,y,shape=0):
+ if shape == 1:
+ # ring
+ x = x*2
+ y = y*2
+ s = (-cos(x**2+y**2)/(x**2+y**2+0.5))
+ elif shape == 2:
+ # swirl
+ x = x*2
+ y = y*2
+ s = (( x*sin( x*x+y*y ) + y*cos( x*x+y*y ) ) / (x**2+y**2+0.5))
+ elif shape == 3:
+ # bumps
+ x = x*2
+ y = y*2
+ s = ((cos( x*pi ) + cos( y*pi ))-0.5)
+ elif shape == 4:
+ # y grad.
+ s = (y*pi)
+ elif shape == 5:
+ # x grad.
+ s = (x*pi)
+ else:
+ # marble
+ s = ((x+y)*5)
+ return s
+
+# marble_noise
+def marble_noise(x,y,z, origin, size, shape, bias, sharpnes, turb, depth, hard, basis ):
+ x = x / size
+ y = y / size
+ z = z / size
+ s = shapes(x,y,shape)
+
+ x += origin[0]
+ y += origin[1]
+ z += origin[2]
+ value = s + turb * turbulence_vector((x,y,z), depth, hard, basis )[0]
+
+ if bias == 1:
+ value = tri_bias( value )
+ elif bias == 2:
+ value = saw_bias( value )
+ else:
+ value = sin_bias( value )
+
+ if sharpnes == 1:
+ value = sharp( value )
+ elif sharpnes == 2:
+ value = sharper( value )
+ else:
+ value = soft( value )
+
+ return value
+
+###------------------------------------------------------------
+# custom noise types
+
+# shattered_hterrain:
+def shattered_hterrain( x,y,z, H, lacunarity, octaves, offset, distort, basis ):
+ d = ( turbulence_vector( ( x, y, z ), 6, 0, 0 )[0] * 0.5 + 0.5 )*distort*0.5
+ t1 = ( turbulence_vector( ( x+d, y+d, z ), 0, 0, 7 )[0] + 0.5 )
+ t2 = ( hetero_terrain(( x*2, y*2, z*2 ), H, lacunarity, octaves, offset, basis )*0.5 )
+ return (( t1*t2 )+t2*0.5) * 0.5
+
+# strata_hterrain
+def strata_hterrain( x,y,z, H, lacunarity, octaves, offset, distort, basis ):
+ value = hetero_terrain(( x, y, z ), H, lacunarity, octaves, offset, basis )*0.5
+ steps = ( sin( value*(distort*5)*pi ) * ( 0.1/(distort*5)*pi ) )
+ return ( value * (1.0-0.5) + steps*0.5 )
+
+###------------------------------------------------------------
+# landscape_gen
+def landscape_gen(x,y,z,falloffsize,options=[0,1.0,1, 0,0,1.0,0,6,1.0,2.0,1.0,2.0,0,0,0, 1.0,0.0,1,0.0,1.0,0,0,0]):
+
+ # options
+ rseed = options[0]
+ nsize = options[1]
+ ntype = int( options[2][0] )
+ nbasis = int( options[3][0] )
+ vlbasis = int( options[4][0] )
+ distortion = options[5]
+ hardnoise = options[6]
+ depth = options[7]
+ dimension = options[8]
+ lacunarity = options[9]
+ offset = options[10]
+ gain = options[11]
+ marblebias = int( options[12][0] )
+ marblesharpnes = int( options[13][0] )
+ marbleshape = int( options[14][0] )
+ invert = options[15]
+ height = options[16]
+ heightoffset = options[17]
+ falloff = int( options[18][0] )
+ sealevel = options[19]
+ platlevel = options[20]
+ strata = options[21]
+ stratatype = options[22]
+ sphere = options[23]
+
+ # origin
+ if rseed == 0:
+ origin = 0.0,0.0,0.0
+ origin_x = 0.0
+ origin_y = 0.0
+ origin_z = 0.0
+ else:
+ # randomise origin
+ seed_set( rseed )
+ origin = random_unit_vector()
+ origin_x = ( 0.5 - origin[0] ) * 1000.0
+ origin_y = ( 0.5 - origin[1] ) * 1000.0
+ origin_z = ( 0.5 - origin[2] ) * 1000.0
+
+ # adjust noise size and origin
+ ncoords = ( x / nsize + origin_x, y / nsize + origin_y, z / nsize + origin_z )
+
+ # noise basis type's
+ if nbasis == 9: nbasis = 14 # to get cellnoise basis you must set 14 instead of 9
+ if vlbasis ==9: vlbasis = 14
+ # noise type's
+ if ntype == 0: value = multi_fractal( ncoords, dimension, lacunarity, depth, nbasis ) * 0.5
+ elif ntype == 1: value = ridged_multi_fractal( ncoords, dimension, lacunarity, depth, offset, gain, nbasis ) * 0.5
+ elif ntype == 2: value = hybrid_multi_fractal( ncoords, dimension, lacunarity, depth, offset, gain, nbasis ) * 0.5
+ elif ntype == 3: value = hetero_terrain( ncoords, dimension, lacunarity, depth, offset, nbasis ) * 0.25
+ elif ntype == 4: value = fractal( ncoords, dimension, lacunarity, depth, nbasis )
+ elif ntype == 5: value = turbulence_vector( ncoords, depth, hardnoise, nbasis )[0]
+ elif ntype == 6: value = vl_vector( ncoords, distortion, nbasis, vlbasis ) + 0.5
+ elif ntype == 7: value = marble_noise( x*2.0/falloffsize,y*2.0/falloffsize,z*2/falloffsize, origin, nsize, marbleshape, marblebias, marblesharpnes, distortion, depth, hardnoise, nbasis )
+ elif ntype == 8: value = shattered_hterrain( ncoords[0], ncoords[1], ncoords[2], dimension, lacunarity, depth, offset, distortion, nbasis )
+ elif ntype == 9: value = strata_hterrain( ncoords[0], ncoords[1], ncoords[2], dimension, lacunarity, depth, offset, distortion, nbasis )
+ else:
+ value = 0.0
+
+ # adjust height
+ if invert !=0:
+ value = (1-value) * height + heightoffset
+ else:
+ value = value * height + heightoffset
+
+ # edge falloff
+ if sphere == 0: # no edge falloff if spherical
+ if falloff != 0:
+ fallofftypes = [ 0, sqrt((x*x)**2+(y*y)**2), sqrt(x*x+y*y), sqrt(y*y), sqrt(x*x) ]
+ dist = fallofftypes[ falloff]
+ if falloff ==1:
+ radius = (falloffsize/2)**2
+ else:
+ radius = falloffsize/2
+ value = value - sealevel
+ if( dist < radius ):
+ dist = dist / radius
+ dist = ( (dist) * (dist) * ( 3-2*(dist) ) )
+ value = ( value - value * dist ) + sealevel
+ else:
+ value = sealevel
+
+ # strata / terrace / layered
+ if stratatype !='0':
+ strata = strata / height
+ if stratatype == '1':
+ strata *= 2
+ steps = ( sin( value*strata*pi ) * ( 0.1/strata*pi ) )
+ value = ( value * (1.0-0.5) + steps*0.5 ) * 2.0
+ elif stratatype == '2':
+ steps = -abs( sin( value*(strata)*pi ) * ( 0.1/(strata)*pi ) )
+ value =( value * (1.0-0.5) + steps*0.5 ) * 2.0
+ elif stratatype == '3':
+ steps = abs( sin( value*(strata)*pi ) * ( 0.1/(strata)*pi ) )
+ value =( value * (1.0-0.5) + steps*0.5 ) * 2.0
+ else:
+ value = value
+
+ # clamp height
+ if ( value < sealevel ): value = sealevel
+ if ( value > platlevel ): value = platlevel
+
+ return value
+
+
+# generate grid
+def grid_gen( sub_d, size_me, options ):
+
+ verts = []
+ faces = []
+ edgeloop_prev = []
+
+ delta = size_me / float(sub_d - 1)
+ start = -(size_me / 2.0)
+
+ for row_x in range(sub_d):
+ edgeloop_cur = []
+ x = start + row_x * delta
+ for row_y in range(sub_d):
+ y = start + row_y * delta
+ z = landscape_gen(x,y,0.0,size_me,options)
+
+ 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
+
+ return verts, faces
+
+
+# generate sphere
+def sphere_gen( sub_d, size_me, options ):
+
+ verts = []
+ faces = []
+ edgeloop_prev = []
+
+ for row_x in range(sub_d):
+ edgeloop_cur = []
+ for row_y in range(sub_d):
+ u = sin(row_y*pi*2/(sub_d-1)) * cos(-pi/2+row_x*pi/(sub_d-1)) * size_me/2
+ v = cos(row_y*pi*2/(sub_d-1)) * cos(-pi/2+row_x*pi/(sub_d-1)) * size_me/2
+ w = sin(-pi/2+row_x*pi/(sub_d-1)) * size_me/2
+ h = landscape_gen(u,v,w,size_me,options) / size_me
+ u,v,w = u+u*h, v+v*h, w+w*h
+
+ edgeloop_cur.append(len(verts))
+ verts.append((u, v, w))
+
+ if len(edgeloop_prev) > 0:
+ faces_row = createFaces(edgeloop_prev, edgeloop_cur)
+ faces.extend(faces_row)
+
+ edgeloop_prev = edgeloop_cur
+
+ return verts, faces
+
+
+###------------------------------------------------------------
+# Add landscape
+class landscape_add(bpy.types.Operator):
+ '''Add a landscape mesh'''
+ bl_idname = "Add_landscape"
+ bl_label = "Landscape"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = "Add landscape mesh"
+
+ # edit - Whether to add or update.
+ edit = BoolProperty(name="",
+ description="",
+ default=False,
+ options={'HIDDEN'})
+
+ # align_matrix for the invoke
+ align_matrix = Matrix()
+
+ # properties
+ AutoUpdate = BoolProperty(name="Mesh update",
+ default=True,
+ description="Update mesh")
+
+ SphereMesh = BoolProperty(name="Sphere",
+ default=False,
+ description="Generate Sphere mesh")
+
+ SmoothMesh = BoolProperty(name="Smooth",
+ default=True,
+ description="Shade smooth")
+
+ Subdivision = IntProperty(name="Subdivisions",
+ min=4,
+ max=6400,
+ default=64,
+ description="Mesh x y subdivisions")
+
+ MeshSize = FloatProperty(name="Mesh Size",
+ min=0.01,
+ max=100000.0,
+ default=2.0,
+ description="Mesh size")
+
+ RandomSeed = IntProperty(name="Random Seed",
+ min=0,
+ max=9999,
+ default=0,
+ description="Randomize noise origin")
+
+ NoiseSize = FloatProperty(name="Noise Size",
+ min=0.01,
+ max=10000.0,
+ default=1.0,
+ description="Noise size")
+
+ NoiseTypes = [
+ ("0","multiFractal","multiFractal"),
+ ("1","ridgedMFractal","ridgedMFractal"),
+ ("2","hybridMFractal","hybridMFractal"),
+ ("3","heteroTerrain","heteroTerrain"),
+ ("4","fBm","fBm"),
+ ("5","Turbulence","Turbulence"),
+ ("6","Distorted Noise","Distorted Noise"),
+ ("7","Marble","Marble"),
+ ("8","Shattered_hTerrain","Shattered_hTerrain"),
+ ("9","Strata_hTerrain","Strata_hTerrain")]
+
+ NoiseType = EnumProperty(name="Type",
+ description="Noise type",
+ items=NoiseTypes)
+
+ BasisTypes = [
+ ("0","Blender","Blender"),
+ ("1","Perlin","Perlin"),
+ ("2","NewPerlin","NewPerlin"),
+ ("3","Voronoi_F1","Voronoi_F1"),
+ ("4","Voronoi_F2","Voronoi_F2"),
+ ("5","Voronoi_F3","Voronoi_F3"),
+ ("6","Voronoi_F4","Voronoi_F4"),
+ ("7","Voronoi_F2-F1","Voronoi_F2-F1"),
+ ("8","Voronoi Crackle","Voronoi Crackle"),
+ ("9","Cellnoise","Cellnoise")]
+ BasisType = EnumProperty(name="Basis",
+ description="Noise basis",
+ items=BasisTypes)
+
+ VLBasisTypes = [
+ ("0","Blender","Blender"),
+ ("1","Perlin","Perlin"),
+ ("2","NewPerlin","NewPerlin"),
+ ("3","Voronoi_F1","Voronoi_F1"),
+ ("4","Voronoi_F2","Voronoi_F2"),
+ ("5","Voronoi_F3","Voronoi_F3"),
+ ("6","Voronoi_F4","Voronoi_F4"),
+ ("7","Voronoi_F2-F1","Voronoi_F2-F1"),
+ ("8","Voronoi Crackle","Voronoi Crackle"),
+ ("9","Cellnoise","Cellnoise")]
+ VLBasisType = EnumProperty(name="VLBasis",
+ description="VLNoise basis",
+ items=VLBasisTypes)
+
+ Distortion = FloatProperty(name="Distortion",
+ min=0.01,
+ max=1000.0,
+ default=1.0,
+ description="Distortion amount")
+
+ HardNoise = BoolProperty(name="Hard",
+ default=True,
+ description="Hard noise")
+
+ NoiseDepth = IntProperty(name="Depth",
+ min=1,
+ max=16,
+ default=6,
+ description="Noise Depth - number of frequencies in the fBm.")
+
+ mDimension = FloatProperty(name="Dimension",
+ min=0.01,
+ max=2.0,
+ default=1.0,
+ description="H - fractal dimension of the roughest areas.")
+
+ mLacunarity = FloatProperty(name="Lacunarity",
+ min=0.01,
+ max=6.0,
+ default=2.0,
+ description="Lacunarity - gap between successive frequencies.")
+
+ mOffset = FloatProperty(name="Offset",
+ min=0.01,
+ max=6.0,
+ default=1.0,
+ description="Offset - raises the terrain from sea level.")
+
+ mGain = FloatProperty(name="Gain",
+ min=0.01,
+ max=6.0,
+ default=1.0,
+ description="Gain - scale factor.")
+
+ BiasTypes = [
+ ("0","Sin","Sin"),
+ ("1","Tri","Tri"),
+ ("2","Saw","Saw")]
+ MarbleBias = EnumProperty(name="Bias",
+ description="Marble bias",
+ items=BiasTypes)
+
+ SharpTypes = [
+ ("0","Soft","Soft"),
+ ("1","Sharp","Sharp"),
+ ("2","Sharper","Sharper")]
+ MarbleSharp = EnumProperty(name="Sharp",
+ description="Marble sharp",
+ items=SharpTypes)
+
+ ShapeTypes = [
+ ("0","Default","Default"),
+ ("1","Ring","Ring"),
+ ("2","Swirl","Swirl"),
+ ("3","Bump","Bump"),
+ ("4","Y","Y"),
+ ("5","X","X")]
+ MarbleShape = EnumProperty(name="Shape",
+ description="Marble shape",
+ items=ShapeTypes)
+
+ Invert = BoolProperty(name="Invert",
+ default=False,
+ description="Invert noise input")
+
+ Height = FloatProperty(name="Height",
+ min=0.01,
+ max=10000.0,
+ default=0.5,
+ description="Height scale")
+
+ Offset = FloatProperty(name="Offset",
+ min=-10000.0,
+ max=10000.0,
+ default=0.0,
+ description="Height offset")
+
+ fallTypes = [
+ ("0","None","None"),
+ ("1","Type 1","Type 1"),
+ ("2","Type 2","Type 2"),
+ ("3","Y","Y"),
+ ("4","X","X")]
+ Falloff = EnumProperty(name="Falloff",
+ description="Edge falloff",
+ default="1",
+ items=fallTypes)
+
+ Sealevel = FloatProperty(name="Sealevel",
+ min=-10000.0,
+ max=10000.0,
+ default=0.0,
+ description="Sealevel")
+
+ Plateaulevel = FloatProperty(name="Plateau",
+ min=-10000.0,
+ max=10000.0,
+ default=1.0,
+ description="Plateau level")
+
+ Strata = FloatProperty(name="Strata",
+ min=0.01,
+ max=1000.0,
+ default=3.0,
+ description="Strata amount")
+
+ StrataTypes = [
+ ("0","None","None"),
+ ("1","Type 1","Type 1"),
+ ("2","Type 2","Type 2"),
+ ("3","Type 3","Type 3")]
+ StrataType = EnumProperty(name="Strata",
+ description="Strata type",
+ default="0",
+ items=StrataTypes)
+
+ ###------------------------------------------------------------
+ # Draw
+ def draw(self, context):
+ layout = self.layout
+
+ box = layout.box()
+ box.prop(self, 'AutoUpdate')
+ box.prop(self, 'SphereMesh')
+ box.prop(self, 'SmoothMesh')
+ box.prop(self, 'Subdivision')
+ box.prop(self, 'MeshSize')
+
+ box = layout.box()
+ box.prop(self, 'NoiseType')
+ if self.NoiseType != '7':
+ box.prop(self, 'BasisType')
+ box.prop(self, 'RandomSeed')
+ box.prop(self, 'NoiseSize')
+ if self.NoiseType == '0':
+ box.prop(self, 'NoiseDepth')
+ box.prop(self, 'mDimension')
+ box.prop(self, 'mLacunarity')
+ if self.NoiseType == '1':
+ box.prop(self, 'NoiseDepth')
+ box.prop(self, 'mDimension')
+ box.prop(self, 'mLacunarity')
+ box.prop(self, 'mOffset')
+ box.prop(self, 'mGain')
+ if self.NoiseType == '2':
+ box.prop(self, 'NoiseDepth')
+ box.prop(self, 'mDimension')
+ box.prop(self, 'mLacunarity')
+ box.prop(self, 'mOffset')
+ box.prop(self, 'mGain')
+ if self.NoiseType == '3':
+ box.prop(self, 'NoiseDepth')
+ box.prop(self, 'mDimension')
+ box.prop(self, 'mLacunarity')
+ box.prop(self, 'mOffset')
+ if self.NoiseType == '4':
+ box.prop(self, 'NoiseDepth')
+ box.prop(self, 'mDimension')
+ box.prop(self, 'mLacunarity')
+ if self.NoiseType == '5':
+ box.prop(self, 'NoiseDepth')
+ box.prop(self, 'HardNoise')
+ if self.NoiseType == '6':
+ box.prop(self, 'VLBasisType')
+ box.prop(self, 'Distortion')
+ if self.NoiseType == '7':
+ box.prop(self, 'MarbleShape')
+ box.prop(self, 'MarbleBias')
+ box.prop(self, 'MarbleSharp')
+ box.prop(self, 'Distortion')
+ box.prop(self, 'NoiseDepth')
+ box.prop(self, 'HardNoise')
+ if self.NoiseType == '8':
+ box.prop(self, 'NoiseDepth')
+ box.prop(self, 'mDimension')
+ box.prop(self, 'mLacunarity')
+ box.prop(self, 'mOffset')
+ box.prop(self, 'Distortion')
+ if self.NoiseType == '9':
+ box.prop(self, 'NoiseDepth')
+ box.prop(self, 'mDimension')
+ box.prop(self, 'mLacunarity')
+ box.prop(self, 'mOffset')
+ box.prop(self, 'Distortion')
+
+ box = layout.box()
+ box.prop(self, 'Invert')
+ box.prop(self, 'Height')
+ box.prop(self, 'Offset')
+ box.prop(self, 'Plateaulevel')
+ box.prop(self, 'Sealevel')
+ if self.SphereMesh == False:
+ box.prop(self, 'Falloff')
+ box.prop(self, 'StrataType')
+ if self.StrataType != '0':
+ box.prop(self, 'Strata')
+
+ ###------------------------------------------------------------
+ # Execute
+ def execute(self, context):
+
+ edit = self.edit
+
+ #mesh update
+ if self.AutoUpdate != 0:
+
+ # turn off undo
+ undo = bpy.context.user_preferences.edit.use_global_undo
+ bpy.context.user_preferences.edit.use_global_undo = False
+
+ # deselect all objects
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # options
+ options = [
+ self.RandomSeed, #0
+ self.NoiseSize, #1
+ self.NoiseType, #2
+ self.BasisType, #3
+ self.VLBasisType, #4
+ self.Distortion, #5
+ self.HardNoise, #6
+ self.NoiseDepth, #7
+ self.mDimension, #8
+ self.mLacunarity, #9
+ self.mOffset, #10
+ self.mGain, #11
+ self.MarbleBias, #12
+ self.MarbleSharp, #13
+ self.MarbleShape, #14
+ self.Invert, #15
+ self.Height, #16
+ self.Offset, #17
+ self.Falloff, #18
+ self.Sealevel, #19
+ self.Plateaulevel, #20
+ self.Strata, #21
+ self.StrataType, #22
+ self.SphereMesh #23
+ ]
+
+ # Main function
+ if self.SphereMesh !=0:
+ # sphere
+ verts, faces = sphere_gen( self.Subdivision, self.MeshSize, options )
+ else:
+ # grid
+ verts, faces = grid_gen( self.Subdivision, self.MeshSize, options )
+
+ # create mesh object
+ obj = create_mesh_object(context, verts, [], faces, "Landscape", edit, self.align_matrix)
+
+ # sphere, remove doubles
+ if self.SphereMesh !=0:
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.remove_doubles(limit=0.0001)
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Shade smooth
+ if self.SmoothMesh !=0:
+ bpy.ops.object.shade_smooth()
+
+ # restore pre operator undo state
+ bpy.context.user_preferences.edit.use_global_undo = undo
+
+ return {'FINISHED'}
+ else:
+ return {'PASS_THROUGH'}
+
+ def invoke(self, context, event):
+ self.align_matrix = align_matrix(context)
+ self.execute(context)
+ return {'FINISHED'}
+
+###------------------------------------------------------------
+# Register
+import space_info
+
+ # Define "Landscape" menu
+def menu_func_landscape(self, context):
+ self.layout.operator(landscape_add.bl_idname, text="Landscape", icon="PLUGIN")
+
+def register():
+ space_info.INFO_MT_mesh_add.append(menu_func_landscape)
+
+def unregister():
+ space_info.INFO_MT_mesh_add.remove(menu_func_landscape)
+
+if __name__ == "__main__":
+ register()
diff --git a/add_mesh_extras.py b/add_mesh_extras.py
new file mode 100644
index 00000000..6892b9cf
--- /dev/null
+++ b/add_mesh_extras.py
@@ -0,0 +1,663 @@
+# ##### 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": "Extras",
+ "author": "Pontiac, Fourmadmen, meta-androcto",
+ "version": (0,4),
+ "blender": (2, 5, 5),
+ "api": 33832,
+ "location": "View3D > Add > Mesh > Extras",
+ "description": "Adds Star, Wedge, & Sqorus objects.",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Add_Mesh/Add_Extra",
+ "tracker_url": "http://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 = Matrix.Translation(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_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):
+
+ # Create mesh geometry
+ verts, faces = add_sqorus(
+ self.hole_size,
+ self.subdivide)
+
+ # Create mesh object (and meshdata)
+ obj = create_mesh_object(context, verts, [], faces, "Sqorus",
+ self.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):
+
+ verts, faces = add_wedge(
+ self.size_x,
+ self.size_y,
+ self.size_z)
+
+ obj = create_mesh_object(context, verts, [], faces, "Wedge",
+ self.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):
+
+ verts, faces = add_star(
+ self.points,
+ self.outer_radius,
+ self.innter_radius,
+ self.height)
+
+ obj = create_mesh_object(context, verts, [], faces, "Star",
+ self.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):
+ # generate mesh
+ verts,faces = trapezohedron(self.segments,
+ self.radius,
+ self.height)
+
+ obj = create_mesh_object(context, verts, [], faces, "Trapazohedron",
+ self.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")
+ 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 "Extras" menu
+def menu_func(self, context):
+ self.layout.menu("INFO_MT_mesh_extras_add", icon="PLUGIN")
+
+
+def register():
+ # Add "Extras" menu to the "Add Mesh" menu
+ space_info.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ # Remove "Extras" 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..1d35047d
--- /dev/null
+++ b/add_mesh_gears.py
@@ -0,0 +1,933 @@
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_addon_info = {
+ "name": "Gears",
+ "author": "Michel J. Anders (varkenvarken)",
+ "version": (2,4,1),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "View3D > Add > Mesh > Gears ",
+ "description": "Adds a mesh Gear to the Add Mesh menu",
+ "warning": "",
+ "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"}
+
+"""
+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.vertex_groups.new('NAME_OF_VERTEXGROUP')
+ob.vertex_groups.assign(vertexgroup_vertex_indices, 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).
+"""
+
+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.Matrix.Translation(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):
+ layout = self.layout
+ box = layout.box()
+ box.prop(self, 'number_of_teeth')
+ box = layout.box()
+ box.prop(self, 'radius')
+ box.prop(self, 'width')
+ box.prop(self, 'base')
+ box = layout.box()
+ box.prop(self, 'dedendum')
+ box.prop(self, 'addendum')
+ box = layout.box()
+ box.prop(self, 'angle')
+ box.prop(self, 'skew')
+ box.prop(self, 'conangle')
+ box.prop(self, 'crown')
+
+
+ def execute(self, context):
+
+ verts, faces, verts_tip, verts_valley = add_gear(
+ self.number_of_teeth,
+ self.radius,
+ self.addendum,
+ self.dedendum,
+ self.base,
+ radians(self.angle),
+ width=self.width,
+ skew=radians(self.skew),
+ conangle=radians(self.conangle),
+ crown=self.crown)
+
+ # Actually create the mesh object from this geometry data.
+ obj = create_mesh_object(context, verts, [], faces, "Gear", self.edit, self.align_matrix)
+
+ # Create vertex groups from stored vertices.
+ tipGroup = obj.vertex_groups.new('Tips')
+ obj.vertex_groups.assign(verts_tip, tipGroup, 1.0, 'ADD')
+
+ valleyGroup = obj.vertex_groups.new('Valleys')
+ obj.vertex_groups.assign(verts_valley, 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):
+ layout = self.layout
+ box = layout.box()
+ box.prop(self, 'number_of_teeth')
+ box.prop(self, 'number_of_rows')
+ box.prop(self, 'radius')
+ box.prop(self, 'row_height')
+ box = layout.box()
+ box.prop(self, 'addendum')
+ box.prop(self, 'dedendum')
+ box = layout.box()
+ box.prop(self, 'angle')
+ box.prop(self, 'skew')
+ box.prop(self, 'crown')
+
+ def execute(self, context):
+
+ verts, faces, verts_tip, verts_valley = add_worm(
+ self.number_of_teeth,
+ self.number_of_rows,
+ self.radius,
+ self.addendum,
+ self.dedendum,
+ radians(self.angle),
+ width=self.row_height,
+ skew=radians(self.skew),
+ crown=self.crown)
+
+ # Actually create the mesh object from this geometry data.
+ obj = create_mesh_object(context, verts, [], faces, "Worm Gear",
+ self.edit, self.align_matrix)
+
+ # Create vertex groups from stored vertices.
+ tipGroup = obj.vertex_groups.new('Tips')
+ obj.vertex_groups.assign(verts_tip, tipGroup, 1.0, 'ADD')
+
+ valleyGroup = obj.vertex_groups.new('Valleys')
+ obj.vertex_groups.assign(verts_valley, 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
+def menu_func(self, context):
+ self.layout.menu("INFO_MT_mesh_gears_add", icon="PLUGIN")
+
+
+def register():
+ # Add "Gears" entry to the "Add Mesh" menu.
+ bpy.types.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ # 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..661e3367
--- /dev/null
+++ b/add_mesh_gemstones.py
@@ -0,0 +1,470 @@
+# ##### 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": "Gemstones",
+ "author": "Pontiac, Fourmadmen, Dreampainter",
+ "version": (0,3),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "View3D > Add > Mesh > Gemstones",
+ "description": "Adds various gemstone (Diamond & Gem) meshes.",
+ "warning": "",
+ "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"}
+
+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 = Matrix.Translation(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):
+ verts, faces = add_diamond(self.segments,
+ self.girdle_radius,
+ self.table_radius,
+ self.crown_height,
+ self.pavilion_height)
+
+ obj = create_mesh_object(context, verts, [], faces,
+ "Diamond", self.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):
+
+ # create mesh
+ verts, faces = add_gem(
+ self.pavilion_radius,
+ self.crown_radius,
+ self.segments,
+ self.pavilion_height,
+ self.crown_height)
+
+ obj = create_mesh_object(context, verts, [], faces, "Gem", self.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
+def menu_func(self, context):
+ self.layout.menu("INFO_MT_mesh_gemstones_add", icon="PLUGIN")
+
+
+def register():
+ # Add "Gemstones" menu to the "Add Mesh" menu
+ space_info.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ # 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..1ef91026
--- /dev/null
+++ b/add_mesh_pipe_joint.py
@@ -0,0 +1,1306 @@
+# ##### 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": "Pipe Joints",
+ "author": "Buerbaum Martin (Pontiac)",
+ "version": (0, 10, 6),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "View3D > Add > Mesh > Pipe Joint",
+ "description": "Adds 5 pipe Joint types to the Add Mesh menu",
+ "warning": "",
+ "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"}
+
+"""
+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.6 - Removed "recall properties" from all functions.
+ Updated various code for new API.
+ API: mathutils.RotationMatrix -> mathutils.Matrix.Rotation
+ API: xxx.selected -> xxx.select
+ API: "invoke" function for each operator.
+ Updated for new bl_addon_info structure.
+ New code for the "align_matrix".
+ made script PEP8 compatible.
+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 mesh.faces.add 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.Matrix.Rotation(-math.pi/2, 4, 'x')
+mesh.transform(rotation_matrix)
+"""
+
+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.Matrix.Translation(context.scene.cursor_location)
+ obj_align = context.user_preferences.edit.object_align
+ if (context.space_data.type == 'VIEW_3D'
+ and obj_align == 'VIEW'):
+ view_mat = context.space_data.region_3d.view_matrix
+ rot = view_mat.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.
+ 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.edit
+
+ radius = self.radius
+ div = self.div
+
+ angle = self.angle
+
+ startLength = self.startLength
+ endLength = self.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.edit
+
+ radius = self.radius
+ div = self.div
+
+ angle = self.angle
+
+ startLength = self.startLength
+ endLength = self.endLength
+ branchLength = self.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.edit
+
+ radius = self.radius
+ div = self.div
+
+ angle1 = self.angle1
+ angle2 = self.angle2
+
+ startLength = self.startLength
+ branch1Length = self.branch1Length
+ branch2Length = self.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.edit
+
+ radius = self.radius
+ div = self.div
+
+ angle1 = self.angle1
+ angle2 = self.angle2
+ angle3 = self.angle3
+
+ startLength = self.startLength
+ branch1Length = self.branch1Length
+ branch2Length = self.branch2Length
+ branch3Length = self.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.edit
+ radius = self.radius
+ div = self.div
+ number = self.number
+ length = self.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
+def menu_func(self, context):
+ self.layout.menu("INFO_MT_mesh_pipe_joints_add", icon="PLUGIN")
+
+
+def register():
+ # Add "Pipe Joints" menu to the "Add Mesh" menu
+ space_info.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ # 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..8c222375
--- /dev/null
+++ b/add_mesh_solid.py
@@ -0,0 +1,901 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_addon_info = {
+ "name": "Regular Solids",
+ "author": "DreamPainter",
+ "version": (1,),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "View3D > Add > Mesh > Regular Solids",
+ "description": "Add a Regular Solid mesh.",
+ "warning": "",
+ "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.use_global_undo = False
+
+
+ #if preset, set preset
+ if self.preset != "0":
+ using = self.p[self.preset]
+ self.source = using[0]
+ self.vTrunc = using[1]
+ self.eTrunc = using[2]
+ self.dual = using[3]
+ self.snub = using[4]
+ self.preset = "0"
+
+ # generate mesh
+ verts,faces = createSolid(self.source,
+ self.vTrunc,
+ self.eTrunc,
+ self.dual,
+ self.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 self.keepSize:
+ rad = self.size/verts[0].length
+ else: rad = self.size
+ verts = [i*rad for i in verts]
+
+ # generate object
+ obj = create_mesh_object(context,verts,[],faces,"Solid",self.edit)
+
+ # vertices will be on top of each other in some cases,
+ # so remove doubles then
+ if ((self.vTrunc == 1) and (self.eTrunc == 0)) or (self.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 self.dual and (self.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.use_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
+
+
+def menu_func(self, context):
+ self.layout.menu(Solids_add_menu.bl_idname, icon="PLUGIN")
+
+
+def register():
+ space_info.INFO_MT_mesh_add.append(menu_func)
+
+def unregister():
+ 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..6992d5d4
--- /dev/null
+++ b/add_mesh_twisted_torus.py
@@ -0,0 +1,362 @@
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_addon_info = {
+ "name": "Twisted Torus",
+ "author": "Paulo_Gomes",
+ "version": (0,11),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "View3D > Add > Mesh ",
+ "description": "Adds a mesh Twisted Torus to the Add Mesh menu",
+ "warning": "",
+ "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"}
+
+"""
+Usage:
+
+* Launch from Add Mesh menu
+
+* Modify parameters as desired or keep defaults
+"""
+
+
+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 = Matrix.Translation(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):
+
+ if self.use_abso == True:
+ extra_helper = (self.abso_major_rad - self.abso_minor_rad) * 0.5
+ self.major_radius = self.abso_minor_rad + extra_helper
+ self.minor_radius = extra_helper
+
+ verts, faces = add_twisted_torus(
+ self.major_radius,
+ self.minor_radius,
+ self.major_segments,
+ self.minor_segments,
+ self.twists)
+
+ # Actually create the mesh object from this geometry data.
+ obj = create_mesh_object(context, verts, [], faces, "TwistedTorus",
+ self.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
+def menu_func(self, context):
+ self.layout.operator(AddTwistedTorus.bl_idname, text="Twisted Torus", icon='MESH_TORUS')
+
+
+def register():
+ bpy.types.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ bpy.types.INFO_MT_mesh_add.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/animation_add_corrective_shape_key.py b/animation_add_corrective_shape_key.py
new file mode 100644
index 00000000..5b990c89
--- /dev/null
+++ b/animation_add_corrective_shape_key.py
@@ -0,0 +1,497 @@
+# ##### 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': 'Corrective shape keys',
+ 'author': 'Ivo Grigull (loolarge), Tal Trachtman',
+ 'version': (1, 0),
+ 'blender': (2, 5, 5),
+ 'location': 'Object Data > Shape Keys (Search: corrective) ',
+ 'description': 'Creates a corrective shape key for the current pose',
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Animation/Corrective_Shape_Key",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=22129&group_id=153&atid=469",
+ 'category': 'Animation'}
+
+"""
+This script transfer the shape from an object (base mesh without
+modifiers) to another object with modifiers (i.e. posed Armature).
+Only two objects must be selected.
+The first selected object will be added to the second selected
+object as a new shape key.
+
+- Original 2.4x script by ? (brecht?)
+- Unpose-function reused from a script by Tal Trachtman in 2007
+ http://www.apexbow.com/randd.html
+- Converted to Blender 2.5 by Ivo Grigull
+
+Limitations:
+- Target mesh may not have any transformation at object level,
+ it will be set to zero.
+- Fast/Armature method does not work with Bone envelopes or dual quaternions,
+ both settings will be disabled in the modifier
+"""
+
+
+import bpy
+import mathutils
+
+
+iterations = 20
+threshold = 1e-16
+
+def reset_transform(ob):
+ m = mathutils.Matrix()
+ ob.matrix_local = m
+
+# flips rotation matrix
+def flip_matrix_direction(m):
+ mat = mathutils.Matrix()
+
+ mat[0][0] = m[0][0]
+ mat[0][1] = m[1][0]
+ mat[0][2] = m[2][0]
+
+ mat[1][0] = m[0][1]
+ mat[1][1] = m[1][1]
+ mat[1][2] = m[2][1]
+
+ mat[2][0] = m[0][2]
+ mat[2][1] = m[1][2]
+ mat[2][2] = m[2][2]
+
+ return mat
+
+# this version is for shape_key data
+def extractX(ob, mesh):
+ x = []
+
+ for i in range(0, len(mesh)):
+ v = mesh[i]
+ x += [mathutils.Vector(v.co)]
+
+ return x
+
+# this version is for mesh data
+def extractX_2(ob, mesh):
+ x = []
+
+ for i in range(0, len(mesh.vertices)):
+ v = mesh.vertices[i]
+ x += [mathutils.Vector(v.co)]
+
+ return x
+
+def extractMappedX(ob, mesh):
+ totvert = len(mesh)
+
+ mesh = ob.create_mesh( bpy.context.scene, True, 'PREVIEW' )
+
+ x = []
+
+ # cheating, the original mapped verts happen
+ # to be at the end of the vertex array
+ for i in range(len(mesh.vertices)-totvert, len(mesh.vertices)):
+ v = mesh.vertices[i]
+ x += [mathutils.Vector(v.co)]
+
+ mesh.user_clear()
+ bpy.data.meshes.remove(mesh)
+
+ return x
+
+def applyX(ob, mesh, x ):
+ for i in range(0, len(mesh)):
+ v = mesh[i]
+ v.co = x[i]
+
+ ob.data.update()
+
+ return x
+
+
+def func_add_corrective_pose_shape( source, target):
+
+ ob_1 = target
+ mesh_1 = target.data
+ ob_2 = source
+ mesh_2 = source.data
+
+ reset_transform(target)
+
+ # If target object doesn't have Basis shape key, create it.
+ try:
+ num_keys = len( mesh_1.shape_keys.keys )
+ except:
+ basis = ob_1.shape_key_add()
+ basis.name = "Basis"
+ ob_1.data.update()
+
+
+ key_index = ob_1.active_shape_key_index
+ # Insert new shape key
+ if key_index == 0:
+ new_shapekey = ob_1.shape_key_add()
+ new_shapekey.name = "Shape_" + ob_2.name
+ new_shapekey_name = new_shapekey.name
+
+ key_index = len(mesh_1.shape_keys.keys)-1
+ ob_1.active_shape_key_index = key_index
+
+ # else, the active shape will be used (updated)
+
+ ob_1.show_only_shape_key = True
+
+ vgroup = ob_1.active_shape_key.vertex_group
+ ob_1.active_shape_key.vertex_group = ""
+
+ mesh_1_key_verts = mesh_1.shape_keys.keys[ key_index ].data
+
+
+ x = extractX(ob_1, mesh_1_key_verts)
+
+ targetx = extractX_2(ob_2, mesh_2)
+
+ for iteration in range(0, iterations):
+ dx = [[], [], [], [], [], []]
+
+ mapx = extractMappedX(ob_1, mesh_1_key_verts)
+
+ # finite differencing in X/Y/Z to get approximate gradient
+ for i in range(0, len(mesh_1.vertices)):
+ epsilon = (targetx[i] - mapx[i]).length
+
+ if epsilon < threshold:
+ epsilon = 0.0
+
+ dx[0] += [x[i] + 0.5*epsilon*mathutils.Vector([1, 0, 0])]
+ dx[1] += [x[i] + 0.5*epsilon*mathutils.Vector([-1, 0, 0])]
+ dx[2] += [x[i] + 0.5*epsilon*mathutils.Vector([0, 1, 0])]
+ dx[3] += [x[i] + 0.5*epsilon*mathutils.Vector([0, -1, 0])]
+ dx[4] += [x[i] + 0.5*epsilon*mathutils.Vector([0, 0, 1])]
+ dx[5] += [x[i] + 0.5*epsilon*mathutils.Vector([0, 0, -1])]
+
+ for j in range(0, 6):
+ applyX(ob_1, mesh_1_key_verts, dx[j] )
+ dx[j] = extractMappedX(ob_1, mesh_1_key_verts)
+
+ # take a step in the direction of the gradient
+ for i in range(0, len(mesh_1.vertices)):
+ epsilon = (targetx[i] - mapx[i]).length
+
+ if epsilon >= threshold:
+ Gx = list((dx[0][i] - dx[1][i])/epsilon)
+ Gy = list((dx[2][i] - dx[3][i])/epsilon)
+ Gz = list((dx[4][i] - dx[5][i])/epsilon)
+ G = mathutils.Matrix(Gx, Gy, Gz)
+ G = flip_matrix_direction(G)
+
+ x[i] += (targetx[i] - mapx[i]) * G
+
+ applyX(ob_1, mesh_1_key_verts, x )
+
+
+ ob_1.active_shape_key.vertex_group = vgroup
+
+ # set the new shape key value to 1.0, so we see the result instantly
+ ob_1.active_shape_key.value = 1.0
+
+ #mesh_1.update()
+ ob_1.show_only_shape_key = False
+
+
+class add_corrective_pose_shape(bpy.types.Operator):
+ '''Adds first object as shape to second object for the current pose while maintaining modifiers (i.e. anisculpt, avoiding crazy space) Beware of slowness!!!'''
+
+ bl_idname = "object.add_corrective_pose_shape"
+ bl_label = "Add object as corrective pose shape"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+
+ if len(context.selected_objects) > 2:
+ print("Select source and target objects please")
+ return {'FINISHED'}
+
+ selection = context.selected_objects
+ target = context.active_object
+ if context.active_object == selection[0]:
+ source = selection[1]
+ else:
+ source = selection[0]
+
+ #~ print(source)
+ #~ print(target)
+ func_add_corrective_pose_shape( source, target)
+
+ return {'FINISHED'}
+
+def func_object_duplicate_flatten_modifiers(ob, scene):
+ mesh = ob.create_mesh( bpy.context.scene, True, 'PREVIEW' )
+ name = ob.name + "_clean"
+ new_object = bpy.data.objects.new( name, mesh)
+ new_object.data = mesh
+ scene.objects.link(new_object)
+ return new_object
+
+class object_duplicate_flatten_modifiers(bpy.types.Operator):
+ '''Duplicates the selected object with modifiers applied'''
+
+ bl_idname = "object.object_duplicate_flatten_modifiers"
+ bl_label = "Duplicate and apply all"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ new_object = func_object_duplicate_flatten_modifiers( context.active_object, context.scene )
+ context.scene.objects.active = new_object
+
+ for n in bpy.data.objects:
+ if n != new_object:
+ n.select = False
+ else:
+ n.select = True
+ return {'FINISHED'}
+
+
+
+
+def flip_matrix_direction_4x4(m):
+ mat = mathutils.Matrix()
+
+ mat[0][0] = m[0][0]
+ mat[0][1] = m[1][0]
+ mat[0][2] = m[2][0]
+ mat[0][3] = m[3][0]
+
+ mat[1][0] = m[0][1]
+ mat[1][1] = m[1][1]
+ mat[1][2] = m[2][1]
+ mat[1][3] = m[3][1]
+
+ mat[2][0] = m[0][2]
+ mat[2][1] = m[1][2]
+ mat[2][2] = m[2][2]
+ mat[2][3] = m[3][2]
+
+ mat[3][0] = m[0][3]
+ mat[3][1] = m[1][3]
+ mat[3][2] = m[2][3]
+ mat[3][3] = m[3][3]
+ return mat
+
+
+def unposeMesh(meshObToUnpose, meshObToUnposeWeightSrc, armatureOb):
+ psdMeshData = meshObToUnpose
+
+ psdMesh = psdMeshData
+ I = mathutils.Matrix() #identity matrix
+
+ meshData = meshObToUnposeWeightSrc.data
+ mesh = meshData
+
+ armData = armatureOb.data
+
+ pose = armatureOb.pose
+ pbones = pose.bones
+
+
+ for index, v in enumerate(mesh.vertices):
+ # above is python shortcut for:index goes up from 0 to tot num of verts in mesh,
+ # with index incrementing by 1 each iteration
+
+ psdMeshVert = psdMesh[index]
+
+ listOfBoneNameWeightPairs = []
+ for n in mesh.vertices[index].groups:
+ try:
+ name = meshObToUnposeWeightSrc.vertex_groups[n.group].name
+ weight = n.weight
+ is_bone = False
+ for i in armData.bones:
+ if i.name == name:
+ is_bone = True
+ break
+ # ignore non-bone vertex groups
+ if is_bone:
+ listOfBoneNameWeightPairs.append( [name, weight] )
+ except:
+ print('error')
+ pass
+
+ weightedAverageDictionary = {}
+ totalWeight = 0
+ for pair in listOfBoneNameWeightPairs:
+ totalWeight += pair[1]
+
+ for pair in listOfBoneNameWeightPairs:
+ if (totalWeight>0): #avoid divide by zero!
+ weightedAverageDictionary[pair[0]] = pair[1]/totalWeight
+ else:
+ weightedAverageDictionary[pair[0]] = 0
+
+ sigma = mathutils.Matrix(I-I) #Matrix filled with zeros
+
+ list = []
+ for n in pbones:
+ list.append(n)
+ list.reverse()
+
+ for pbone in list:
+ if pbone.name in weightedAverageDictionary:
+ #~ print("found key %s", pbone.name)
+ vertexWeight = weightedAverageDictionary[pbone.name]
+ m = pbone.matrix_channel.copy()
+ #m = flip_matrix_direction_4x4(m)
+ sigma += (m - I) * vertexWeight
+
+ else:
+ pass
+ #~ print("no key for bone " + pbone.name)
+
+ sigma = I + sigma
+ sigma.invert()
+ psdMeshVert.co = psdMeshVert.co * sigma
+
+
+
+def func_add_corrective_pose_shape_fast(source, target):
+
+
+ reset_transform(target)
+
+ # If target object doesn't have Basis shape key, create it.
+ try:
+ num_keys = len( target.data.shape_keys.keys )
+ except:
+ basis = target.shape_key_add()
+ basis.name = "Basis"
+ target.data.update()
+
+ key_index = target.active_shape_key_index
+
+ if key_index == 0:
+
+ # Insert new shape key
+ new_shapekey = target.shape_key_add()
+ new_shapekey.name = "Shape_" + source.name
+ new_shapekey_name = new_shapekey.name
+
+ key_index = len(target.data.shape_keys.keys)-1
+ target.active_shape_key_index = key_index
+
+ # else, the active shape will be used (updated)
+
+ target.show_only_shape_key = True
+
+ shape_key_verts = target.data.shape_keys.keys[ key_index ].data
+
+ try:
+ vgroup = target.active_shape_key.vertex_group
+ target.active_shape_key.vertex_group = ''
+ except:
+ print("blub")
+ pass
+
+ # copy the local vertex positions to the new shape
+ verts = source.data.vertices
+ for n in range( len(verts)):
+ shape_key_verts[n].co = verts[n].co
+
+ # go to all armature modifies and unpose the shape
+ for n in target.modifiers:
+ if n.type == 'ARMATURE' and n.show_viewport:
+ #~ print("got one")
+ n.use_bone_envelopes = False
+ n.use_deform_preserve_volume = False
+ n.use_vertex_groups = True
+ armature = n.object
+ unposeMesh( shape_key_verts, target, armature)
+ break
+
+ # set the new shape key value to 1.0, so we see the result instantly
+ target.data.shape_keys.keys[ target.active_shape_key_index].value = 1.0
+
+ try:
+ target.active_shape_key.vertex_group = vgroup
+ except:
+ #~ print("bluba")
+ pass
+
+ target.show_only_shape_key = False
+ target.data.update()
+
+
+
+class add_corrective_pose_shape_fast(bpy.types.Operator):
+ '''Adds 1st object as shape to 2nd object as pose shape (only 1 armature)'''
+
+ bl_idname = "object.add_corrective_pose_shape_fast"
+ bl_label = "Add object as corrective shape faster"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+
+ if len(context.selected_objects) > 2:
+ print("Select source and target objects please")
+ return {'FINISHED'}
+
+ selection = context.selected_objects
+ target = context.active_object
+ if context.active_object == selection[0]:
+ source = selection[1]
+ else:
+ source = selection[0]
+
+ print(source)
+ print(target)
+ func_add_corrective_pose_shape_fast( source, target)
+
+ return {'FINISHED'}
+
+
+
+
+## GUI
+def vgroups_draw(self, context):
+ layout = self.layout
+
+ layout.row().operator("object.add_corrective_pose_shape_fast", text='Add as corrective pose-shape (fast, armatures only)', icon='COPY_ID') # icon is not ideal
+ layout.row().operator("object.add_corrective_pose_shape", text='Add as corrective pose-shape (slow, all modifiers)', icon='COPY_ID') # icon is not ideal
+
+def modifiers_draw(self, context):
+ layout = self.layout
+
+ layout.operator("object.object_duplicate_flatten_modifiers" )
+
+
+def register():
+ bpy.types.MESH_MT_shape_key_specials.append( vgroups_draw )
+ bpy.types.DATA_PT_modifiers.append( modifiers_draw )
+
+def unregister():
+ pass
diff --git a/animation_rotobezier.py b/animation_rotobezier.py
new file mode 100644
index 00000000..0c709890
--- /dev/null
+++ b/animation_rotobezier.py
@@ -0,0 +1,378 @@
+# ##### 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': 'RotoBezier',
+ 'author': 'Daniel Salazar <zanqdo@gmail.com>',
+ 'version': (0, 8),
+ 'blender': (2, 5, 5),
+ 'api': 33232,
+ 'location': 'Select a Curve: Toolbar > RotoBezier panel',
+ 'description': 'Allows animation of Bezier and NURBS curves',
+ 'warning': '',
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/'\
+ 'Scripts/Animation/RotoBezier',
+ 'tracker_url': 'http://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=24839&group_id=153&atid=469',
+ 'category': 'Animation'}
+
+'''
+-------------------------------------------------------------------------
+Thanks to Campbell Barton for his API additions and fixes
+Daniel Salazar - ZanQdo
+
+Rev 0.1 Initial release
+Rev 0.2 New make matte object tools and convenient display toggles
+Rev 0.3 Tool to clear all animation from the curve
+Rev 0.4 Moved from curve properties to toolbar
+Rev 0.5 Added pass index property
+Rev 0.6 Re-arranged UI
+Rev 0.7 Adding options for what properties to keyframe
+Rev 0.8 Allowing to key NURBS
+-------------------------------------------------------------------------
+'''
+
+import bpy
+from bpy.props import *
+
+
+#
+# Property Definitions
+#
+bpy.types.WindowManager.key_points = BoolProperty(
+ name="Points",
+ description="Insert keyframes on point locations",
+ default=True)
+
+bpy.types.WindowManager.key_bevel = BoolProperty(
+ name="Bevel",
+ description="Insert keyframes on point bevel (Shrink/Fatten)",
+ default=False)
+
+bpy.types.WindowManager.key_tilt = BoolProperty(
+ name="Tilt",
+ description="Insert keyframes on point tilt",
+ default=False)
+
+
+#
+# GUI (Panel)
+#
+class VIEW3D_PT_rotobezier(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+ bl_label = 'RotoBezier'
+
+ # show this add-on only in the Camera-Data-Panel
+ @classmethod
+ def poll(self, context):
+ if context.active_object:
+ return context.active_object.type == 'CURVE'
+
+ # draw the gui
+ def draw(self, context):
+ layout = self.layout
+
+ col = layout.column(align=True)
+
+ col.label(text="Keyframing:")
+ row = col.row()
+ row.prop(context.window_manager, "key_points")
+ row.prop(context.window_manager, "key_bevel")
+ row.prop(context.window_manager, "key_tilt")
+
+ row = col.row()
+ row.operator('curve.insert_keyframe_rotobezier', icon='KEY_HLT')
+ row.operator('curve.delete_keyframe_rotobezier', icon='KEY_DEHLT')
+ row = layout.row()
+ row.operator('curve.clear_animation_rotobezier', icon='X')
+
+ col = layout.column()
+
+ col.label(text="Display:")
+ row = col.row()
+ row.operator('curve.toggle_draw_rotobezier', icon='MESH_CIRCLE')
+
+ if context.mode == 'EDIT_CURVE':
+ row.operator('curve.toggle_handles_rotobezier', icon='CURVE_BEZCIRCLE')
+
+ col = layout.column(align=True)
+
+ col.label(text="Tools:")
+ row = col.row()
+ row.operator('curve.make_white_matte_rotobezier')
+ row.operator('curve.make_black_matte_rotobezier')
+ row = layout.row()
+ ob = context.active_object
+ row.prop(ob, "pass_index")
+
+
+class CURVE_OT_insert_keyframe_rotobezier(bpy.types.Operator):
+ bl_label = 'Insert'
+ bl_idname = 'curve.insert_keyframe_rotobezier'
+ bl_description = 'Insert a RotoBezier Keyframe'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # on mouse up:
+ def invoke(self, context, event):
+ self.execute(context)
+ return {'FINISHED'}
+
+ @classmethod
+ def poll(cls, context):
+ return (context.active_object.type == 'CURVE')
+
+ def execute(op, context):
+
+ Obj = context.active_object
+
+ Mode = False
+ if context.mode != 'OBJECT':
+ Mode = not Mode
+ bpy.ops.object.editmode_toggle()
+ Data = Obj.data
+
+ for Spline in Data.splines:
+ if Spline.type == 'BEZIER':
+ for CV in Spline.bezier_points:
+ if context.window_manager.key_points:
+ CV.keyframe_insert('co')
+ CV.keyframe_insert('handle_left')
+ CV.keyframe_insert('handle_right')
+ if context.window_manager.key_bevel:
+ CV.keyframe_insert('radius')
+ if context.window_manager.key_tilt:
+ CV.keyframe_insert('tilt')
+
+ elif Spline.type == 'NURBS':
+ for CV in Spline.points:
+ if context.window_manager.key_points:
+ CV.keyframe_insert('co')
+ if context.window_manager.key_bevel:
+ CV.keyframe_insert('radius')
+ if context.window_manager.key_tilt:
+ CV.keyframe_insert('tilt')
+
+ if Mode:
+ bpy.ops.object.editmode_toggle()
+
+
+ return {'FINISHED'}
+
+
+class CURVE_OT_delete_keyframe_rotobezier(bpy.types.Operator):
+ bl_label = 'Delete'
+ bl_idname = 'curve.delete_keyframe_rotobezier'
+ bl_description = 'Delete a RotoBezier Keyframe'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # on mouse up:
+ def invoke(self, context, event):
+ self.execute(context)
+ return {'FINISHED'}
+
+ @classmethod
+ def poll(cls, context):
+ return (context.active_object.type == 'CURVE')
+
+ def execute(op, context):
+
+ Obj = context.active_object
+
+ Mode = False
+ if context.mode != 'OBJECT':
+ Mode = not Mode
+ bpy.ops.object.editmode_toggle()
+ Data = Obj.data
+
+ for Spline in Data.splines:
+ if Spline.type == 'BEZIER':
+ for CV in Spline.bezier_points:
+ if context.window_manager.key_points:
+ CV.keyframe_delete('co')
+ CV.keyframe_delete('handle_left')
+ CV.keyframe_delete('handle_right')
+ if context.window_manager.key_bevel:
+ CV.keyframe_delete('radius')
+ if context.window_manager.key_tilt:
+ CV.keyframe_delete('tilt')
+
+ elif Spline.type == 'NURBS':
+ for CV in Spline.points:
+ if context.window_manager.key_points:
+ CV.keyframe_delete('co')
+ if context.window_manager.key_bevel:
+ CV.keyframe_delete('radius')
+ if context.window_manager.key_tilt:
+ CV.keyframe_delete('tilt')
+
+ if Mode:
+ bpy.ops.object.editmode_toggle()
+
+ return {'FINISHED'}
+
+
+class CURVE_OT_clear_animation_rotobezier(bpy.types.Operator):
+ bl_label = 'Clear Animation'
+ bl_idname = 'curve.clear_animation_rotobezier'
+ bl_description = 'Clear all animation from the curve'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # on mouse up:
+ def invoke(self, context, event):
+ wm = context.window_manager
+ return wm.invoke_confirm(self, event)
+
+ def execute(op, context):
+ Data = context.active_object.data
+ Data.animation_data_clear()
+ return {'FINISHED'}
+
+
+def MakeMatte (Type):
+ '''
+ Matte Material Assignment Function
+ '''
+
+ Obj = bpy.context.active_object
+
+ # Material
+
+ if Type == 'White':
+ MatName = 'RotoBezier_WhiteMatte'
+ MatCol = (1,1,1)
+
+ elif Type == 'Black':
+ MatName = 'RotoBezier_BlackMatte'
+ MatCol = (0,0,0)
+
+ if bpy.data.materials.get(MatName):
+ Mat = bpy.data.materials[MatName]
+ if not Obj.material_slots:
+ bpy.ops.object.material_slot_add()
+ Obj.material_slots[0].material = Mat
+
+ else:
+ Mat = bpy.data.materials.new(MatName)
+ Mat.diffuse_color = MatCol
+ Mat.use_shadeless = True
+ Mat.use_raytrace = False
+ Mat.use_shadows = False
+ Mat.use_cast_buffer_shadows = False
+ Mat.use_cast_approximate = False
+
+ if not Obj.material_slots:
+ bpy.ops.object.material_slot_add()
+
+ Obj.material_slots[0].material = Mat
+
+ # Settings
+ Curve = Obj.data
+
+ Curve.dimensions = '2D'
+ Curve.use_fill_front = False
+ Curve.use_fill_back = False
+
+
+class CURVE_OT_make_white_matte_rotobezier(bpy.types.Operator):
+ bl_label = 'White Matte'
+ bl_idname = 'curve.make_white_matte_rotobezier'
+ bl_description = 'Make this curve a white matte'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # on mouse up:
+ def invoke(self, context, event):
+ self.execute(context)
+ return {'FINISHED'}
+
+ def execute(op, context):
+ MakeMatte('White')
+ return {'FINISHED'}
+
+
+class CURVE_OT_make_black_matte_rotobezier(bpy.types.Operator):
+ bl_label = 'Black Matte'
+ bl_idname = 'curve.make_black_matte_rotobezier'
+ bl_description = 'Make this curve a black matte'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # on mouse up:
+ def invoke(self, context, event):
+ self.execute(context)
+ return {'FINISHED'}
+
+ def execute(op, context):
+ MakeMatte('Black')
+ return {'FINISHED'}
+
+
+class CURVE_OT_toggle_handles_rotobezier(bpy.types.Operator):
+ bl_label = 'Handles'
+ bl_idname = 'curve.toggle_handles_rotobezier'
+ bl_description = 'Toggle the curve handles display in edit mode'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # on mouse up:
+ def invoke(self, context, event):
+ self.execute(context)
+ return {'FINISHED'}
+
+ def execute(op, context):
+ Obj = context.active_object
+ Curve = Obj.data
+ if Curve.show_handles:
+ Curve.show_handles = False
+ else:
+ Curve.show_handles = True
+ return {'FINISHED'}
+
+
+class CURVE_OT_toggle_draw_rotobezier(bpy.types.Operator):
+ bl_label = 'Filling'
+ bl_idname = 'curve.toggle_draw_rotobezier'
+ bl_description = 'Toggle the curve display mode between Wire and Solid'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ # on mouse up:
+ def invoke(self, context, event):
+ self.execute(context)
+ return {'FINISHED'}
+
+ def execute(op, context):
+ Obj = context.active_object
+
+ if Obj.draw_type == 'SOLID':
+ Obj.draw_type = 'WIRE'
+
+ elif Obj.draw_type == 'WIRE':
+ Obj.draw_type = 'SOLID'
+
+ else:
+ Obj.draw_type = 'WIRE'
+
+ return {'FINISHED'}
+
+
+def register():
+ pass
+
+def unregister():
+ pass
+
+if __name__ == "__main__":
+ register()
diff --git a/curve_simplify.py b/curve_simplify.py
new file mode 100644
index 00000000..f774c0cd
--- /dev/null
+++ b/curve_simplify.py
@@ -0,0 +1,593 @@
+# ##### 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": "Simplify curves",
+ "author": "testscreenings",
+ "version": (1,),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "Toolshelf > search > simplify curves",
+ "description": "This script simplifies 3D curves and fcurves",
+ "warning": "",
+ "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"}
+
+"""
+This script simplifies Curves.
+"""
+
+####################################################
+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.use_endpoint_u = spline.use_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):
+ layout = self.layout
+ col = layout.column()
+ col.label('Mode:')
+ col.prop(self, 'mode', expand=True)
+ if self.mode == 'distance':
+ box = layout.box()
+ box.label(self.mode, icon='ARROW_LEFTRIGHT')
+ box.prop(self, 'error', expand=True)
+ if self.mode == 'curvature':
+ box = layout.box()
+ box.label('degree', icon='SMOOTHCURVE')
+ box.prop(self, 'pointsNr', expand=True)
+ box.label('threshold', icon='PARTICLE_PATH')
+ box.prop(self, 'k_thresh', expand=True)
+ box.label('distance', icon='ARROW_LEFTRIGHT')
+ box.prop(self, 'dis_error', expand=True)
+ col = layout.column()
+ '''
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column()
+ col.prop(self, 'error', expand=True)
+
+ ## Check for animdata
+ @classmethod
+ def poll(cls, 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.mode, #0
+ self.mode, #1
+ self.k_thresh, #2
+ self.pointsNr, #3
+ self.error, #4
+ self.degreeOut, #6
+ self.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):
+ layout = self.layout
+ col = layout.column()
+ col.label('Mode:')
+ col.prop(self, 'mode', expand=True)
+ if self.mode == 'distance':
+ box = layout.box()
+ box.label(self.mode, icon='ARROW_LEFTRIGHT')
+ box.prop(self, 'error', expand=True)
+ if self.mode == 'curvature':
+ box = layout.box()
+ box.label('degree', icon='SMOOTHCURVE')
+ box.prop(self, 'pointsNr', expand=True)
+ box.label('threshold', icon='PARTICLE_PATH')
+ box.prop(self, 'k_thresh', expand=True)
+ box.label('distance', icon='ARROW_LEFTRIGHT')
+ box.prop(self, 'dis_error', expand=True)
+ col = layout.column()
+ col.separator()
+ col.prop(self, 'output', text='Output', icon='OUTLINER_OB_CURVE')
+ if self.output == 'NURBS':
+ col.prop(self, 'degreeOut', expand=True)
+ col.prop(self, 'keepShort', expand=True)
+ '''
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column()
+ col.prop(self, 'error', expand=True)
+ col.prop(self, 'output', text='Output', icon='OUTLINER_OB_CURVE')
+ if self.output == 'NURBS':
+ col.prop(self, 'degreeOut', expand=True)
+ col.prop(self, 'keepShort', expand=True)
+
+
+ ## Check for curve
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return (obj and obj.type == 'CURVE')
+
+ ## execute
+ def execute(self, context):
+ #print("------START------")
+
+ options = [
+ self.mode, #0
+ self.output, #1
+ self.k_thresh, #2
+ self.pointsNr, #3
+ self.error, #4
+ self.degreeOut, #5
+ self.dis_error, #6
+ self.keepShort] #7
+
+
+ bpy.context.user_preferences.edit.use_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.use_global_undo = True
+
+ #print("-------END-------")
+ return {'FINISHED'}
+
+#################################################
+#### REGISTER ###################################
+#################################################
+def register():
+ pass
+
+def unregister():
+ pass
+
+if __name__ == "__main__":
+ register()
diff --git a/game_engine_save_as_runtime.py b/game_engine_save_as_runtime.py
new file mode 100644
index 00000000..eb9e0494
--- /dev/null
+++ b/game_engine_save_as_runtime.py
@@ -0,0 +1,218 @@
+# ##### 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': 'Save As Runtime',
+ 'author': 'Mitchell Stokes (Moguri)',
+ 'version': (0, 3, 0),
+ 'blender': (2, 5, 6),
+ 'api': 34057,
+ 'location': 'File > Export',
+ 'description': 'Bundle a .blend file with the Blenderplayer',
+ 'warning': '',
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/'\
+ 'Scripts/Game_Engine/Save_As_Runtime',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=23564&group_id=153&atid=469',
+ 'category': 'Game Engine'}
+
+import bpy
+import os
+import sys
+import shutil
+
+
+def WriteAppleRuntime(player_path, output_path, copy_python, overwrite_lib):
+ # Enforce the extension
+ if not output_path.endswith('.app'):
+ output_path += '.app'
+
+ # Use the system's cp command to preserve some meta-data
+ os.system('cp -R "%s" "%s"' % (player_path, output_path))
+
+ bpy.ops.wm.save_as_mainfile(filepath=output_path+"/Contents/Resources/game.blend", copy=True)
+
+ # Copy bundled Python
+ blender_dir = os.path.dirname(bpy.app.binary_path)
+
+ if copy_python:
+ print("Copying Python files...", end=" ")
+ src = os.path.join(blender_dir, bpy.app.version_string.split()[0], "python", "lib")
+ dst = os.path.join(output_path, "Contents", "MacOS", "lib")
+
+ if os.path.exists(dst):
+ if overwrite_lib:
+ shutil.rmtree(dst)
+ shutil.copytree(src, dst, ignore=lambda dir, contents: [i for i in contents if i.endswith('.pyc')])
+ else:
+ shutil.copytree(src, dst, ignore=lambda dir, contents: [i for i in contents if i.endswith('.pyc')])
+
+ print("done")
+
+
+def WriteRuntime(player_path, output_path, copy_python, overwrite_lib, copy_dlls):
+ import struct
+
+ # Check the paths
+ if not os.path.isfile(player_path) and not(os.path.exists(player_path) and player_path.endswith('.app')):
+ print("The player could not be found! Runtime not saved.")
+ return
+
+ # Check if we're bundling a .app
+ if player_path.endswith('.app'):
+ WriteAppleRuntime(player_path, output_path, copy_python, overwrite_lib)
+ return
+
+ # Enforce "exe" extension on Windows
+ if player_path.endswith('.exe') and not output_path.endswith('.exe'):
+ output_path += '.exe'
+
+ # Get the player's binary and the offset for the blend
+ file = open(player_path, 'rb')
+ player_d = file.read()
+ offset = file.tell()
+ file.close()
+
+ # Create a tmp blend file (Blenderplayer doesn't like compressed blends)
+ blend_path = bpy.path.clean_name(output_path)
+ bpy.ops.wm.save_as_mainfile(filepath=blend_path, compress=False, copy=True)
+ blend_path += '.blend'
+
+ # Get the blend data
+ blend_file = open(blend_path, 'rb')
+ blend_d = blend_file.read()
+ blend_file.close()
+
+ # Get rid of the tmp blend, we're done with it
+ os.remove(blend_path)
+
+ # Create a new file for the bundled runtime
+ output = open(output_path, 'wb')
+
+ # Write the player and blend data to the new runtime
+ print("Writing runtime...", end=" ")
+ output.write(player_d)
+ output.write(blend_d)
+
+ # Store the offset (an int is 4 bytes, so we split it up into 4 bytes and save it)
+ output.write(struct.pack('B', (offset>>24)&0xFF))
+ output.write(struct.pack('B', (offset>>16)&0xFF))
+ output.write(struct.pack('B', (offset>>8)&0xFF))
+ output.write(struct.pack('B', (offset>>0)&0xFF))
+
+ # Stuff for the runtime
+ output.write(b'BRUNTIME')
+ output.close()
+
+ print("done")
+
+ # Make the runtime executable on Linux
+ if os.name == 'posix':
+ os.chmod(output_path, 0o755)
+
+ # Copy bundled Python
+ blender_dir = os.path.dirname(bpy.app.binary_path)
+ runtime_dir = os.path.dirname(output_path)
+
+ if copy_python:
+ print("Copying Python files...", end=" ")
+ src = os.path.join(blender_dir, bpy.app.version_string.split()[0], "python", "lib")
+ dst = os.path.join(runtime_dir, "lib")
+
+ if os.path.exists(dst):
+ if overwrite_lib:
+ shutil.rmtree(dst)
+ shutil.copytree(src, dst, ignore=lambda dir, contents: [i for i in contents if i.endswith('.pyc')])
+ else:
+ shutil.copytree(src, dst, ignore=lambda dir, contents: [i for i in contents if i.endswith('.pyc')])
+
+ print("done")
+
+ # And DLLs
+ if copy_dlls:
+ print("Copying DLLs...", end=" ")
+ for file in [i for i in os.listdir(blender_dir) if i.endswith('.dll')]:
+ src = os.path.join(blender_dir, file)
+ dst = os.path.join(runtime_dir, file)
+ shutil.copy2(src, dst)
+
+ print("done")
+
+from bpy.props import *
+
+
+class SaveAsRuntime(bpy.types.Operator):
+ bl_idname = "wm.save_as_runtime"
+ bl_label = "Save As Runtime"
+ bl_options = {'REGISTER'}
+
+ if sys.platform == 'darwin':
+ blender_bin_dir = '/'+os.path.join(*bpy.app.binary_path.split('/')[0:-4])
+ ext = '.app'
+ else:
+ blender_bin_path = bpy.app.binary_path
+ blender_bin_dir = os.path.dirname(blender_bin_path)
+ ext = os.path.splitext(blender_bin_path)[-1]
+
+ default_player_path = os.path.join(blender_bin_dir, 'blenderplayer' + ext)
+ player_path = StringProperty(name="Player Path", description="The path to the player to use", default=default_player_path)
+ filepath = StringProperty(name="Output Path", description="Where to save the runtime", default="")
+ copy_python = BoolProperty(name="Copy Python", description="Copy bundle Python with the runtime", default=True)
+ overwrite_lib = BoolProperty(name="Overwrite 'lib' folder", description="Overwrites the lib folder (if one exists) with the bundled Python lib folder", default=False)
+
+ # Only Windows has dlls to copy
+ if ext == '.exe':
+ copy_dlls = BoolProperty(name="Copy DLLs", description="Copy all needed DLLs with the runtime", default=True)
+ else:
+ copy_dlls = False
+
+ def execute(self, context):
+ import time
+ start_time = time.clock()
+ print("Saving runtime to", self.properties.filepath)
+ WriteRuntime(self.properties.player_path,
+ self.properties.filepath,
+ self.properties.copy_python,
+ self.properties.overwrite_lib,
+ self.copy_dlls)
+ print("Finished in %.4fs" % (time.clock()-start_time))
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ wm.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+
+def menu_func(self, context):
+
+ ext = '.app' if sys.platform == 'darwin' else os.path.splitext(bpy.app.binary_path)[-1]
+ default_blend_path = bpy.data.filepath.replace(".blend", ext)
+ self.layout.operator(SaveAsRuntime.bl_idname, text=SaveAsRuntime.bl_label).filepath = default_blend_path
+
+
+def register():
+ bpy.types.INFO_MT_file_export.append(menu_func)
+
+
+def unregister():
+ bpy.types.INFO_MT_file_export.remove(menu_func)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/io_anim_camera.py b/io_anim_camera.py
new file mode 100644
index 00000000..ebe6e8e1
--- /dev/null
+++ b/io_anim_camera.py
@@ -0,0 +1,165 @@
+# ##### 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),
+ "api": 31847,
+ "location": "File > Export > Camera Animation",
+ "description": "Export Cameras & Markers",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/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.frame_set(f)
+ fw("# new frame\n")
+ fw("scene.frame_set(%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.new('%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 *
+from io_utils import ExportHelper
+
+
+class CameraExporter(bpy.types.Operator, ExportHelper):
+ '''Save a python script which re-creartes cameras and markers elsewhere'''
+ bl_idname = "export_animation.cameras"
+ bl_label = "Export Camera & Markers"
+
+ filename_ext = ".py"
+ filter_glob = StringProperty(default="*.py", options={'HIDDEN'})
+
+ 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.filepath, self.frame_start, self.frame_end, self.only_selected)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ self.frame_start = context.scene.frame_start
+ self.frame_end = context.scene.frame_end
+
+ wm = context.window_manager
+ wm.fileselect_add(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.INFO_MT_file_export.append(menu_export)
+
+
+def unregister():
+ bpy.types.INFO_MT_file_export.remove(menu_export)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/io_coat3D/__init__.py b/io_coat3D/__init__.py
new file mode 100644
index 00000000..48cf3056
--- /dev/null
+++ b/io_coat3D/__init__.py
@@ -0,0 +1,225 @@
+# ##### 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": "3D-Coat Applink",
+ "author": "Kalle-Samuli Riihikoski (haikalle)",
+ "version": (1, 61),
+ "blender": (2, 5, 4),
+ "api": 31667,
+ "location": "Scene -> 3D-Coat Applink",
+ "description": "Transfer data between 3D-Coat/Blender",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/3dcoat_applink",
+ "tracker_url": "https://projects.blender.org/tracker/?"\
+ "func=detail&aid=24446&group_id=153&atid=469",
+ "category": "Import-Export"}
+
+
+if "bpy" in locals():
+ import imp
+ imp.reload(coat)
+ imp.reload(tex)
+else:
+ from . import coat
+ from . import tex
+
+import bpy
+from bpy.props import *
+
+
+def register():
+
+ bpy.coat3D = dict()
+ bpy.coat3D['active_coat'] = ''
+ bpy.coat3D['status'] = 0
+ bpy.coat3D['was'] = ''
+ bpy.coat3D['exchange'] = ''
+ bpy.coat3D['export_off'] = 0
+
+ class coat3D(bpy.types.IDPropertyGroup):
+ pass
+
+ bpy.types.Object.coat3D= PointerProperty(
+ name= "Applink Variables",
+ type= coat3D,
+ description= "Applink variables"
+ )
+
+ coat3D.objpath = StringProperty(
+ name="Object_Path",
+ default= ""
+ )
+
+ coat3D.coatpath = StringProperty(
+ name="Coat_Path",
+ default= ""
+ )
+
+
+ class coat3D(bpy.types.IDPropertyGroup):
+ pass
+
+ bpy.types.Scene.coat3D= PointerProperty(
+ name= "Applink Variables",
+ type= coat3D,
+ description= "Applink variables"
+ )
+
+ coat3D.objectdir = StringProperty(
+ name="ObjectPath",
+ subtype="FILE_PATH",
+ default= ""
+ )
+
+ coat3D.exchangedir = StringProperty(
+ name="FilePath",
+ subtype="DIR_PATH",
+ default= ""
+ )
+
+ coat3D.wasactive = StringProperty(
+ name="Pass active object",
+ default= ""
+ )
+
+ coat3D.export_on = BoolProperty(
+ name="Export_On",
+ description="Add Modifiers and export.",
+ default= False
+ )
+
+ coat3D.smooth_on = BoolProperty(
+ name="Auto Smooth",
+ description="Add Modifiers and export.",
+ default= True
+ )
+
+ coat3D.exportfile = BoolProperty(
+ name="No Import File",
+ description="Add Modifiers and export.",
+ default= False
+ )
+
+ coat3D.importmod = BoolProperty(
+ name="Remove Modifiers",
+ description="Import and add modifiers.",
+ default= True
+ )
+
+ coat3D.exportmod = BoolProperty(
+ name="Modifiers",
+ description="Export modifiers.",
+ default= False
+ )
+
+ coat3D.export_pos = BoolProperty(
+ name="Remember Position",
+ description="Remember position.",
+ default= True
+ )
+
+ coat3D.importtextures = BoolProperty(
+ name="Bring Textures",
+ description="Import Textures.",
+ default= True
+ )
+
+ coat3D.exportover = BoolProperty(
+ name="Export Obj",
+ description="Import Textures.",
+ default= False
+ )
+
+ coat3D.importmesh = BoolProperty(
+ name="Mesh",
+ description="Import Mesh.",
+ default= True
+ )
+
+ #copy location
+
+ coat3D.cursor = FloatVectorProperty(
+ name="Cursor",
+ description="Location.",
+ subtype="XYZ",
+ default=(0.0, 0.0, 0.0)
+ )
+
+ coat3D.loca = FloatVectorProperty(
+ name="location",
+ description="Location.",
+ subtype="XYZ",
+ default=(0.0, 0.0, 0.0)
+ )
+
+ coat3D.rota = FloatVectorProperty(
+ name="location",
+ description="Location.",
+ subtype="EULER",
+ default=(0.0, 0.0, 0.0)
+ )
+
+ coat3D.scal = FloatVectorProperty(
+ name="location",
+ description="Location.",
+ subtype="XYZ",
+ default=(0.0, 0.0, 0.0)
+ )
+
+ coat3D.dime = FloatVectorProperty(
+ name="dimension",
+ description="Dimension.",
+ subtype="XYZ",
+ default=(0.0, 0.0, 0.0)
+ )
+
+ coat3D.type = EnumProperty( name= "Export Type",
+ description= "Diffrent Export Types.",
+ items=(
+ ("ppp", "Per-Pixel Painting", ""),
+ ("mv", "Microvertex Painting", ""),
+ ("ptex", "Ptex Painting", ""),
+ ("uv", "UV-Mapping", ""),
+ ("ref", "Reference Mesh", ""),
+ ("retopo", "Retopo mesh as new layer", ""),
+ ("vox", "Mesh As Voxel Object", ""),
+ ("alpha", "Mesh As New Pen Alpha", ""),
+ ("prim", "Mesh As Voxel Primitive", ""),
+ ("curv", "Mesh As a Curve Profile", ""),
+ ("autopo", "Mesh For Auto-retopology", ""),
+ ),
+ default= "ppp"
+ )
+
+
+def unregister():
+ import bpy
+
+ del bpy.types.Object.coat3D
+ del bpy.types.Scene.coat3D
+ del bpy.coat3D
+
+
+
+if __name__ == "__main__":
+ register()
+
+
+
diff --git a/io_coat3D/coat.py b/io_coat3D/coat.py
new file mode 100644
index 00000000..6e9cad3d
--- /dev/null
+++ b/io_coat3D/coat.py
@@ -0,0 +1,463 @@
+# scene_blend_info.py Copyright (C) 2010, Mariano Hidalgo
+#
+# Show Information About the Blend.
+# ***** 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 *****
+
+import bpy
+from bpy.props import *
+from io_coat3D import tex
+import os
+import linecache
+
+class ObjectButtonsPanel():
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "object"
+
+class SCENE_PT_Borgleader(ObjectButtonsPanel,bpy.types.Panel):
+ bl_label = "3D-Coat Applink"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ def draw(self, context):
+ layout = self.layout
+ scene = context.scene
+ me = context.scene.objects
+ mat_list = []
+ import_no = 0
+ coat = bpy.coat3D
+ coat3D = bpy.context.scene.coat3D
+
+ if(os.path.isdir(coat3D.exchangedir)):
+ foldder = coat3D.exchangedir
+ if(foldder.rfind('Exchange') >= 0):
+ coat['exchange'] = foldder
+ coat['status'] = 1
+ else:
+ coat['status'] = 0
+ else:
+ coat['status'] = 0
+ #Here you add your GUI
+ row = layout.row()
+ row.prop(coat3D,"type",text = "")
+ row = layout.row()
+ colL = row.column()
+ colR = row.column()
+ if(context.selected_objects):
+ if(context.selected_objects[0].type == 'MESH'):
+ colL.active = True
+ else:
+ colL.active = False
+ else:
+ colL.active = False
+ colL.operator("exportbutton", text="Export")
+ colL.label(text="Export Settings:")
+
+ colL.prop(coat3D,"exportover")
+ if(coat3D.exportover):
+ colL.prop(coat3D,"exportmod")
+ colL.prop(coat3D,"exportfile")
+ colL.prop(coat3D,"export_pos")
+
+
+ if(bpy.context.active_object):
+ colR.active = True
+ else:
+ colR.active = False
+
+ colR.operator("importbutton", text="Import")
+ colR.label(text="Import Settings:")
+ colR.prop(coat3D,"importmesh")
+ colR.prop(coat3D,"importmod")
+ colR.prop(coat3D,"smooth_on")
+ colR.prop(coat3D,"importtextures")
+ row = layout.row()
+ colL = row.column()
+ colM = row.column()
+ colR = row.column()
+ colL.operator("deltex",text="Del Tex")
+ if(bpy.context.active_object):
+ colM.active = True
+ if(bpy.context.active_object.coat3D.coatpath and os.path.isfile(bpy.context.active_object.coat3D.coatpath)):
+ colR.active = True
+ if(coat['active_coat'] == bpy.context.active_object.coat3D.coatpath):
+ colR.operator("load3b", text="Active 3b")
+ else:
+ colR.operator("load3b", text="Load 3b")
+ else:
+ colR.active = False
+ colR.operator("no3b",text="No 3b")
+ else:
+ colM.active = False
+ colR.active = False
+ colR.operator("no3b",text="")
+ colM.operator("pickname",text="Object name")
+ row = layout.row()
+ row.label(text="Object Path:")
+ row = layout.row()
+ row.prop(coat3D,"objectdir",text="")
+ row = layout.row()
+
+ if(coat['status'] == 1):
+ row.label(text="Exchange Folder: connected")
+ Blender_folder = ("%s%sBlender"%(coat3D.exchangedir,os.sep))
+ Blender_export = Blender_folder
+ Blender_export += ('%sexport.txt'%(os.sep))
+
+ if(not(os.path.isdir(Blender_folder))):
+ os.makedirs(Blender_folder)
+ Blender_folder = os.path.join(Blender_folder,"run.txt")
+ file = open(Blender_folder, "w")
+ file.close()
+
+ if(os.path.isfile(Blender_export)):
+ obj_path =''
+ obj_pathh = open(Blender_export)
+ for line in obj_pathh:
+ obj_path = line
+ break
+ obj_pathh.close()
+ print("%s"%obj_path)
+ export = obj_path
+ mod_time = os.path.getmtime(obj_path)
+ mtl_list = obj_path.replace('.obj','.mtl')
+ if(os.path.isfile(mtl_list)):
+ os.remove(mtl_list)
+
+ for palikka in bpy.context.scene.objects:
+ if(palikka.type == 'MESH'):
+ if(palikka.coat3D.objpath == export):
+ import_no = 1
+ target = palikka
+ break
+
+ if(import_no):
+ new_obj = palikka
+ import_no = 0
+ else:
+ bpy.ops.import_scene.obj(filepath=obj_path)
+ new_obj = scene.objects[0]
+ os.remove(Blender_export)
+
+ bpy.context.scene.objects.active = new_obj
+ bpy.context.active_object.coat3D.objpath = obj_path
+
+ if(coat3D.smooth_on):
+ bpy.ops.object.shade_smooth()
+ else:
+ bpy.ops.object.shade_flat()
+
+ Blender_tex = ("%s%stextures.txt"%(coat3D.exchangedir,os.sep))
+ mat_list.append(new_obj.material_slots[0].material)
+ tex.gettex(mat_list, new_obj, scene,export)
+
+
+
+
+
+ if(coat['status'] == 0):
+ row.label(text="Exchange Folder: not connected")
+ row = layout.row()
+ row.prop(coat3D,"exchangedir",text="")
+ row = layout.row()
+ row.label(text="Author: haikalle@gmail.com")
+
+
+class SCENE_OT_export(bpy.types.Operator):
+ bl_idname = "exportbutton"
+ bl_label = "Export your custom property"
+ bl_description = "Export your custom property"
+
+
+ def invoke(self, context, event):
+ checkname = ''
+ coat3D = bpy.context.scene.coat3D
+ coat = bpy.coat3D
+ scene = context.scene
+ coat3D.export_on = False
+ activeobj = bpy.context.active_object.name
+ obj = scene.objects[activeobj]
+
+ importfile = coat3D.exchangedir
+ texturefile = coat3D.exchangedir
+ importfile += ('%simport.txt'%(os.sep))
+ texturefile += ('%stextures.txt'%(os.sep))
+ if(os.path.isfile(texturefile)):
+ os.remove(texturefile)
+
+ checkname = coat3D.objectdir
+
+ if(coat3D.objectdir[-4:] != '.obj'):
+ checkname += ('%s.obj'%(activeobj))
+
+ if(not(os.path.isfile(checkname)) or coat3D.exportover):
+
+
+ bpy.ops.export_scene.obj(filepath=checkname,use_selection=True,
+ use_modifiers=coat3D.exportmod,use_blen_objects=False, group_by_material= True,
+ use_materials = False,keep_vertex_order = True)
+ coat3D.export_on = True
+
+ if(not(coat3D.exportover)):
+ coat3D.loca = obj.location
+ coat3D.rota = obj.rotation_euler
+ coat3D.scal = obj.scale
+ coat['export_off'] = 1
+ else:
+ coat['export_off'] = 0
+
+
+
+ if(coat3D.exportfile == False):
+ file = open(importfile, "w")
+ file.write("%s"%(checkname))
+ file.write("\n%s"%(checkname))
+ file.write("\n[%s]"%(coat3D.type))
+ file.close()
+ coat3D.objectdir = checkname
+ bpy.context.active_object.coat3D.objpath = coat3D.objectdir
+
+ return('FINISHED')
+
+
+class SCENE_OT_import(bpy.types.Operator):
+ bl_idname = "importbutton"
+ bl_label = "import your custom property"
+ bl_description = "import your custom property"
+
+ def invoke(self, context, event):
+ scene = context.scene
+ coat3D = bpy.context.scene.coat3D
+ coat = bpy.coat3D
+ activeobj = bpy.context.active_object.name
+ mat_list = []
+ scene.objects[activeobj].select = True
+ objekti = scene.objects[activeobj]
+ coat3D.loca = objekti.location
+ coat3D.rota = objekti.rotation_euler
+
+ exportfile = coat3D.exchangedir
+ exportfile += ('%sexport.txt'%(os.sep))
+ if(os.path.isfile(exportfile)):
+ export_file = open(exportfile)
+ for line in export_file:
+ if line.rfind('.3b'):
+ objekti.coat3D.coatpath = line
+ coat['active_coat'] = line
+ export_file.close()
+ os.remove(exportfile)
+
+
+
+
+ if(objekti.material_slots):
+ for obj_mat in objekti.material_slots:
+ mat_list.append(obj_mat.material)
+ act_mat_index = objekti.active_material_index
+
+
+ if(coat3D.importmesh and os.path.isfile(coat3D.objectdir)):
+ mtl = coat3D.objectdir
+ mtl = mtl.replace('.obj','.mtl')
+ if(os.path.isfile(mtl)):
+ os.remove(mtl)
+
+
+ bpy.ops.import_scene.obj(filepath=coat3D.objectdir)
+ obj_proxy = scene.objects[0]
+ proxy_mat = obj_proxy.material_slots[0].material
+ obj_proxy.data.materials.pop(0)
+ proxy_mat.user_clear()
+ bpy.data.materials.remove(proxy_mat)
+ bpy.ops.object.select_all(action='TOGGLE')
+ if(coat3D.export_pos):
+ scene.objects.active = objekti
+ objekti.select = True
+ coat3D.cursor = bpy.context.scene.cursor_location
+ bpy.context.scene.cursor_location = (0.0,0.0,0.0)
+ bpy.ops.object.origin_set(type='ORIGIN_CURSOR', center='MEDIAN')
+
+ scene.objects.active = obj_proxy
+
+ obj_data = objekti.data.id_data
+ objekti.data = obj_proxy.data.id_data
+
+ if(coat3D.export_on):
+ objekti.scale = (1,1,1)
+ objekti.rotation_euler = (0,0,0)
+
+ if(bpy.data.meshes[obj_data.name].users == 0):
+ bpy.data.meshes.remove(obj_data)
+ objekti.data.id_data.name = obj_data.name
+
+ bpy.ops.object.select_all(action='TOGGLE')
+
+ obj_proxy.select = True
+ bpy.ops.object.delete()
+ objekti.select = True
+ bpy.context.scene.objects.active = objekti
+ if(coat3D.export_on):
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
+ coat3D.export_on = False
+
+ else:
+ objekti.location = coat3D.loca
+ objekti.rotation_euler = coat3D.rota
+ bpy.ops.object.origin_set(type='GEOMETRY_ORIGIN', center='MEDIAN')
+ else:
+ scene.objects.active = obj_proxy
+
+ obj_data = objekti.data.id_data
+ objekti.data = obj_proxy.data.id_data
+ if(bpy.data.meshes[obj_data.name].users == 0):
+ bpy.data.meshes.remove(obj_data)
+ objekti.data.id_data.name = obj_data.name
+
+ obj_proxy.select = True
+ bpy.ops.object.delete()
+ objekti.select = True
+ bpy.context.scene.objects.active = objekti
+
+
+
+ if(coat3D.smooth_on):
+ bpy.ops.object.shade_smooth()
+ else:
+ bpy.ops.object.shade_flat()
+
+ if(coat3D.importmesh and not(os.path.isfile(coat3D.objectdir))):
+ coat3D.importmesh = False
+
+ if(mat_list and coat3D.importmesh):
+ for mat_one in mat_list:
+ objekti.data.materials.append(mat_one)
+ objekti.active_material_index = act_mat_index
+
+ if(mat_list):
+ for obj_mate in objekti.material_slots:
+ for tex_slot in obj_mate.material.texture_slots:
+ if(hasattr(tex_slot,'texture')):
+ if(tex_slot.texture.type == 'IMAGE'):
+ tex_slot.texture.image.reload()
+
+
+ if(coat3D.importmod):
+ mod_list = []
+ for mod_index in objekti.modifiers:
+ objekti.modifiers.remove(mod_index)
+
+
+
+ if(coat3D.importtextures):
+ export = ''
+ tex.gettex(mat_list,objekti,scene,export)
+
+ if(coat['export_off']):
+ objekti.location = coat3D.loca
+ objekti.rotation_euler = coat3D.rota
+ objekti.scale = coat3D.scal
+ coat['export_off'] = 0
+
+ return('FINISHED')
+
+class SCENE_OT_load3b(bpy.types.Operator):
+ bl_idname = "load3b"
+ bl_label = "Loads 3b linked into object"
+ bl_description = "Loads 3b linked into object"
+
+
+ def invoke(self, context, event):
+ checkname = ''
+ coat3D = bpy.context.scene.coat3D
+ scene = context.scene
+ importfile = coat3D.exchangedir
+ importfile += ('%simport.txt'%(os.sep))
+
+ coat_path = bpy.context.active_object.coat3D.coatpath
+
+ file = open(importfile, "w")
+ file.write("%s"%(coat_path))
+ file.write("\n%s"%(coat_path))
+ file.write("\n[3B]")
+ file.close()
+
+
+ coat['active_coat'] = coat_path
+
+
+ return('FINISHED')
+
+class SCENE_OT_no3b(bpy.types.Operator):
+ bl_idname = "no3b"
+ bl_label = "Loads 3b linked into object"
+ bl_description = "Loads 3b linked into object"
+
+
+ def invoke(self, context, event):
+ scene = context.scene
+
+ return('FINISHED')
+
+class SCENE_OT_pickname(bpy.types.Operator):
+ bl_idname = "pickname"
+ bl_label = "Picks Object's name into path"
+ bl_description = "Loads 3b linked into object"
+
+
+ def invoke(self, context, event):
+ coat3D = bpy.context.scene.coat3D
+ scene = context.scene
+ new_name = os.path.dirname(coat3D.objectdir) + os.sep
+ new_name += ("%s.obj"%(bpy.context.active_object.name))
+ coat3D.objectdir = new_name
+
+ return('FINISHED')
+
+class SCENE_OT_deltex(bpy.types.Operator):
+ bl_idname = "deltex"
+ bl_label = "Picks Object's name into path"
+ bl_description = "Loads 3b linked into object"
+
+
+ def invoke(self, context, event):
+ coat3D = bpy.context.scene.coat3D
+ scene = context.scene
+ nimi = tex.objname(coat3D.objectdir)
+ osoite = os.path.dirname(coat3D.objectdir) + os.sep
+ just_nimi = tex.justname(nimi)
+ just_nimi += '_'
+
+ files = os.listdir(osoite)
+ for i in files:
+ if(i.rfind(just_nimi) >= 0):
+ del_osoite = osoite + i
+ os.remove(del_osoite)
+
+ return('FINISHED')
+
+def register():
+ pass
+
+def unregister():
+ pass
+
+if __name__ == "__main__":
+ register()
diff --git a/io_coat3D/tex.py b/io_coat3D/tex.py
new file mode 100644
index 00000000..a420f6f5
--- /dev/null
+++ b/io_coat3D/tex.py
@@ -0,0 +1,371 @@
+import bpy
+import os
+import filecmp
+
+
+def objname(path):
+
+ path2 = os.path.dirname(path) + os.sep
+ pituus = len(path2)
+ nimi = path[pituus:]
+
+ return nimi
+
+def justname(name):
+ monesko = name.rfind('.')
+ justname = name[:monesko]
+ return justname
+
+def setgallery():
+ newname =''
+ tex_name =[]
+ index_tex = 0
+ for tt in bpy.data.textures:
+ tex_name.append(tt.name)
+ return tex_name
+
+def find_index(objekti):
+ luku = 0
+ for tex in objekti.active_material.texture_slots:
+ if(not(hasattr(tex,'texture'))):
+ break
+ luku = luku +1
+
+
+ return luku
+
+def gettex(mat_list, objekti, scene,export):
+
+ coat3D = bpy.context.scene.coat3D
+
+ if(bpy.context.scene.render.engine == 'VRAY_RENDER' or bpy.context.scene.render.engine == 'VRAY_RENDER_PREVIEW'):
+ vray = True
+ else:
+ vray = False
+
+
+
+ take_color = 0;
+ take_spec = 0;
+ take_normal = 0;
+ take_disp = 0;
+
+ bring_color = 1;
+ bring_spec = 1;
+ bring_normal = 1;
+ bring_disp = 1;
+
+ texcoat = {}
+ texcoat['color'] = []
+ texcoat['specular'] = []
+ texcoat['nmap'] = []
+ texcoat['disp'] = []
+ texu = []
+
+ if(export):
+ objekti.coat3D.objpath = export
+ nimi = objname(export)
+ osoite = os.path.dirname(export) + os.sep
+ for mate in objekti.material_slots:
+ for tex_slot in mate.material.texture_slots:
+ if(hasattr(tex_slot,'texture')):
+ if(tex_slot.texture.type == 'IMAGE'):
+ tex_slot.texture.image.reload()
+ else:
+ nimi = objname(coat3D.objectdir)
+ osoite = os.path.dirname(coat3D.objectdir) + os.sep
+ just_nimi = justname(nimi)
+ just_nimi += '_'
+ just_nimi_len = len(just_nimi)
+
+
+ if(len(objekti.material_slots) != 0):
+ for obj_tex in objekti.active_material.texture_slots:
+ if(hasattr(obj_tex,'texture')):
+ if(obj_tex.texture):
+ if(obj_tex.use_map_color_diffuse):
+ bring_color = 0;
+ if(obj_tex.use_map_specular):
+ bring_spec = 0;
+ if(obj_tex.use_map_normal):
+ bring_normal = 0;
+ if(obj_tex.use_map_displacement):
+ bring_disp = 0;
+
+ files = os.listdir(osoite)
+ for i in files:
+ tui = i[:just_nimi_len]
+ if(tui == just_nimi):
+ texu.append(i)
+
+ for yy in texu:
+ minimi = (yy.rfind('_'))+1
+ maksimi = (yy.rfind('.'))
+ tex_name = yy[minimi:maksimi]
+ koko = ''
+ koko += osoite
+ koko += yy
+ texcoat[tex_name].append(koko)
+ #date = os.path.getmtime(texcoat[tex_name][0])
+
+ if((texcoat['color'] or texcoat['nmap'] or texcoat['disp'] or texcoat['specular']) and (len(objekti.material_slots)) == 0):
+ new_mat = ("%s_Material"%(objekti.name))
+ bpy.data.materials.new(new_mat)
+ ki = bpy.data.materials[new_mat]
+ objekti.data.materials.append(ki)
+
+
+
+ if(bring_color == 1 and texcoat['color']):
+ name_tex ='Color_'
+ num = []
+
+ index = find_index(objekti)
+
+
+ tex = bpy.ops.Texture
+ objekti.active_material.texture_slots.create(index)
+ total_mat = len(objekti.active_material.texture_slots.items())
+ useold = ''
+
+ for seekco in bpy.data.textures:
+ if((seekco.name[:6] == 'Color_') and (seekco.users_material == ())):
+ useold = seekco
+
+
+
+ if(useold == ''):
+
+ tex_name = setgallery()
+
+ for num_tex in tex_name:
+ if(num_tex[:6] == 'Color_'):
+ num.append(num_tex)
+ luku_tex = len(num)
+ name_tex = ('Color_%s'%(luku_tex))
+
+ bpy.ops.image.new(name=name_tex)
+ bpy.data.images[name_tex].filepath = texcoat['color'][0]
+ bpy.data.images[name_tex].source = 'FILE'
+
+
+ bpy.data.textures.new(name_tex,type='IMAGE')
+ objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex]
+ objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex]
+
+ if(objekti.data.uv_textures.active):
+ objekti.active_material.texture_slots[index].texture_coords = 'UV'
+ objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
+
+ objekti.active_material.texture_slots[index].texture.image.reload()
+
+
+ elif(useold != ''):
+
+ objekti.active_material.texture_slots[index].texture = useold
+ objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['color'][0]
+ if(objekti.data.uv_textures.active):
+ objekti.active_material.texture_slots[index].texture_coords = 'UV'
+ objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
+
+ if(bring_normal == 1 and texcoat['nmap']):
+ name_tex ='Normal_'
+ num = []
+
+ index = find_index(objekti)
+
+
+ tex = bpy.ops.Texture
+ objekti.active_material.texture_slots.create(index)
+ total_mat = len(objekti.active_material.texture_slots.items())
+ useold = ''
+
+ for seekco in bpy.data.textures:
+ if((seekco.name[:7] == 'Normal_') and (seekco.users_material == ())):
+ useold = seekco
+
+
+
+ if(useold == ''):
+
+ tex_name = setgallery()
+
+ for num_tex in tex_name:
+ if(num_tex[:7] == 'Normal_'):
+ num.append(num_tex)
+ luku_tex = len(num)
+ name_tex = ('Normal_%s'%(luku_tex))
+
+ bpy.ops.image.new(name=name_tex)
+ bpy.data.images[name_tex].filepath = texcoat['nmap'][0]
+ bpy.data.images[name_tex].source = 'FILE'
+
+
+ bpy.data.textures.new(name_tex,type='IMAGE')
+ objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex]
+ objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex]
+
+ if(objekti.data.uv_textures.active):
+ objekti.active_material.texture_slots[index].texture_coords = 'UV'
+ objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
+ objekti.active_material.texture_slots[index].use_map_color_diffuse = False
+ objekti.active_material.texture_slots[index].use_map_normal = True
+
+ objekti.active_material.texture_slots[index].texture.image.reload()
+ if(vray):
+ bpy.data.textures[name_tex].vray_slot.BRDFBump.map_type = 'TANGENT'
+
+ else:
+ bpy.data.textures[name_tex].use_normal_map = True
+ bpy.data.textures[name_tex].normal_space = 'TANGENT'
+
+
+ elif(useold != ''):
+
+ objekti.active_material.texture_slots[index].texture = useold
+ objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['nmap'][0]
+ if(objekti.data.uv_textures.active):
+ objekti.active_material.texture_slots[index].texture_coords = 'UV'
+ objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
+ objekti.active_material.texture_slots[index].use_map_color_diffuse = False
+ objekti.active_material.texture_slots[index].use_map_normal = True
+
+
+ if(bring_spec == 1 and texcoat['specular']):
+ name_tex ='Specular_'
+ num = []
+
+ index = find_index(objekti)
+
+
+ tex = bpy.ops.Texture
+ objekti.active_material.texture_slots.create(index)
+ total_mat = len(objekti.active_material.texture_slots.items())
+ useold = ''
+
+ for seekco in bpy.data.textures:
+ if((seekco.name[:9] == 'Specular_') and (seekco.users_material == ())):
+ useold = seekco
+
+
+
+
+ if(useold == ''):
+
+ tex_name = setgallery()
+
+ for num_tex in tex_name:
+ if(num_tex[:9] == 'Specular_'):
+ num.append(num_tex)
+ luku_tex = len(num)
+ name_tex = ('Specular_%s'%(luku_tex))
+
+ bpy.ops.image.new(name=name_tex)
+ bpy.data.images[name_tex].filepath = texcoat['specular'][0]
+ bpy.data.images[name_tex].source = 'FILE'
+
+
+ bpy.data.textures.new(name_tex,type='IMAGE')
+ objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex]
+ objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex]
+
+ if(objekti.data.uv_textures.active):
+ objekti.active_material.texture_slots[index].texture_coords = 'UV'
+ objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
+ objekti.active_material.texture_slots[index].use_map_color_diffuse = False
+ objekti.active_material.texture_slots[index].use_map_specular = True
+
+ objekti.active_material.texture_slots[index].texture.image.reload()
+
+
+ elif(useold != ''):
+
+ objekti.active_material.texture_slots[index].texture = useold
+ objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['specular'][0]
+ if(objekti.data.uv_textures.active):
+ objekti.active_material.texture_slots[index].texture_coords = 'UV'
+ objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
+ objekti.active_material.texture_slots[index].use_map_color_diffuse = False
+ objekti.active_material.texture_slots[index].use_map_specular = True
+
+ if(bring_disp == 1 and texcoat['disp']):
+ name_tex ='Displacement_'
+ num = []
+
+ index = find_index(objekti)
+
+
+ tex = bpy.ops.Texture
+ objekti.active_material.texture_slots.create(index)
+ total_mat = len(objekti.active_material.texture_slots.items())
+ useold = ''
+
+ for seekco in bpy.data.textures:
+ if((seekco.name[:13] == 'Displacement_') and (seekco.users_material == ())):
+ useold = seekco
+
+
+
+
+ if(useold == ''):
+
+ tex_name = setgallery()
+
+ for num_tex in tex_name:
+ if(num_tex[:13] == 'Displacement_'):
+ num.append(num_tex)
+ luku_tex = len(num)
+ name_tex = ('Displacement_%s'%(luku_tex))
+
+ bpy.ops.image.new(name=name_tex)
+ bpy.data.images[name_tex].filepath = texcoat['disp'][0]
+ bpy.data.images[name_tex].source = 'FILE'
+
+
+ bpy.data.textures.new(name_tex,type='IMAGE')
+ objekti.active_material.texture_slots[index].texture = bpy.data.textures[name_tex]
+ objekti.active_material.texture_slots[index].texture.image = bpy.data.images[name_tex]
+
+ if(objekti.data.uv_textures.active):
+ objekti.active_material.texture_slots[index].texture_coords = 'UV'
+ objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
+ objekti.active_material.texture_slots[index].use_map_color_diffuse = False
+ objekti.active_material.texture_slots[index].use_map_displacement = True
+
+ objekti.active_material.texture_slots[index].texture.image.reload()
+
+
+ elif(useold != ''):
+
+ objekti.active_material.texture_slots[index].texture = useold
+ objekti.active_material.texture_slots[index].texture.image.filepath = texcoat['disp'][0]
+ if(objekti.data.uv_textures.active):
+ objekti.active_material.texture_slots[index].texture_coords = 'UV'
+ objekti.active_material.texture_slots[index].uv_layer = objekti.data.uv_textures.active.name
+ objekti.active_material.texture_slots[index].use_map_color_diffuse = False
+ objekti.active_material.texture_slots[index].use_map_displacement = True
+
+ if(vray):
+ objekti.active_material.texture_slots[index].texture.use_interpolation = False
+ objekti.active_material.texture_slots[index].displacement_factor = 0.05
+
+
+ else:
+ disp_modi = ''
+ for seek_modi in objekti.modifiers:
+ if(seek_modi.type == 'DISPLACE'):
+ disp_modi = seek_modi
+ break
+ if(disp_modi):
+ disp_modi.texture = objekti.active_material.texture_slots[index].texture
+ if(objekti.data.uv_textures.active):
+ disp_modi.texture_coords = 'UV'
+ disp_modi.uv_layer = objekti.data.uv_textures.active.name
+ else:
+ objekti.modifiers.new('Displace',type='DISPLACE')
+ objekti.modifiers['Displace'].texture = objekti.active_material.texture_slots[index].texture
+ if(objekti.data.uv_textures.active):
+ objekti.modifiers['Displace'].texture_coords = 'UV'
+ objekti.modifiers['Displace'].uv_layer = objekti.data.uv_textures.active.name
+
+ return('FINISHED')
diff --git a/io_convert_image_to_mesh_img/__init__.py b/io_convert_image_to_mesh_img/__init__.py
new file mode 100644
index 00000000..92d3572c
--- /dev/null
+++ b/io_convert_image_to_mesh_img/__init__.py
@@ -0,0 +1,128 @@
+# ##### 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": "HiRISE DTM from PDS IMG",
+ "author": "Tim Spriggs (tims@uahirise.org)",
+ "version": (0, 1, 2),
+ "blender": (2, 5, 3),
+ "api": 31998,
+ "location": "File > Import > HiRISE DTM from PDS IMG (.IMG)",
+ "description": "Import a HiRISE DTM formatted as a PDS IMG file",
+ "warning": "May consume a lot of memory",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/HiRISE_DTM_from_PDS_IMG",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=24897&group_id=153&atid=469",
+ "category": "Import-Export"}
+
+
+# Revision History:
+# 0.1.1 - make default import 12x12 bin (fast) to not consume too much memory
+# by default (TJS - 2010-12-07)
+# 0.1.2 - included into svn under the tree:
+# trunk/py/scripts/addons/io_convert_image_to_mesh_img
+# may be moved out to contrib once the blender downloader works well
+# (TJS - 2010-12-14)
+
+
+if "bpy" in locals():
+ import imp
+ imp.reload(import_img)
+else:
+ from . import import_img
+
+
+import bpy
+from bpy.props import *
+from io_utils import ImportHelper
+
+
+class ImportHiRISEIMGDTM(bpy.types.Operator, ImportHelper):
+ '''Import a HiRISE DTM formatted as a PDS IMG file'''
+ bl_idname = "import_shape.img"
+ bl_label = "Import HiRISE DTM from PDS IMG"
+
+ filename_ext = ".IMG"
+ filter_glob = StringProperty(default="*.IMG", options={'HIDDEN'})
+
+ scale = FloatProperty(name="Scale",
+ description="Scale the IMG by this value",
+ min=0.0001,
+ max=10.0,
+ soft_min=0.001,
+ soft_max=100.0,
+ default=0.01)
+
+ bin_mode = EnumProperty(items=(
+ ('NONE', "None", "Don't bin the image"),
+ ('BIN2', "2x2", "use 2x2 binning to import the mesh"),
+ ('BIN6', "6x6", "use 6x6 binning to import the mesh"),
+ ('BIN6-FAST', "6x6 Fast", "use one sample per 6x6 region"),
+ ('BIN12', "12x12", "use 12x12 binning to import the mesh"),
+ ('BIN12-FAST', "12x12 Fast", "use one sample per 12x12 region"),
+ ),
+ name="Binning",
+ description="Import Binning.",
+ default='BIN12-FAST'
+ )
+
+ #red_material = BoolProperty(name="Mars Red Mesh",
+ # description="Set the mesh as a 'Mars' red value",
+ # default=True
+ # )
+
+ ## TODO: add support for cropping on import when the checkbox is checked
+ # do_crop = BoolProperty(name="Crop Image", description="Crop the image during import", ... )
+ ## we only want these visible when the above is "true"
+ # crop_x = IntProperty(name="X", description="Offset from left side of image")
+ # crop_y = IntProperty(name="Y", description="Offset from top of image")
+ # crop_w = IntProperty(name="Width", description="width of cropped operation")
+ # crop_h = IntProperty(name="Height", description="height of cropped region")
+ ## This is also a bit ugly and maybe an anti-pattern. The problem is that
+ ## importing a HiRISE DTM at full resolution will likely kill any mortal user with
+ ## less than 16 GB RAM and getting at specific features in a DTM at full res
+ ## may prove beneficial. Someday most mortals will have 16GB RAM.
+ ## -TJS 2010-11-23
+
+ def execute(self, context):
+ filepath = self.filepath
+ filepath = bpy.path.ensure_ext(filepath, self.filename_ext)
+
+ return import_img.load(self, context,
+ filepath=self.filepath,
+ scale=self.scale,
+ bin_mode=self.bin_mode,
+ cropVars=False,
+ # marsRed=self.red_material
+ marsRed=False
+ )
+
+## How to register the script inside of Blender
+
+def menu_import(self, context):
+ self.layout.operator(ImportHiRISEIMGDTM.bl_idname, text="HiRISE DTM from PDS IMG (*.IMG)")
+
+def register():
+ bpy.types.INFO_MT_file_import.append(menu_import)
+
+def unregister():
+ bpy.types.INFO_MT_file_import.remove(menu_import)
+
+if __name__ == "__main__":
+ register()
diff --git a/io_convert_image_to_mesh_img/import_img.py b/io_convert_image_to_mesh_img/import_img.py
new file mode 100644
index 00000000..40813bf1
--- /dev/null
+++ b/io_convert_image_to_mesh_img/import_img.py
@@ -0,0 +1,786 @@
+# ##### 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 can import a HiRISE DTM .IMG file.
+"""
+
+import bpy
+from bpy.props import *
+
+from struct import pack, unpack, unpack_from
+import os
+import queue, threading
+
+class image_props:
+ ''' keeps track of image attributes throughout the hirise_dtm_helper class '''
+ def __init__(self, name, dimensions, pixel_scale):
+ self.name( name )
+ self.dims( dimensions )
+ self.processed_dims( dimensions )
+ self.pixel_scale( pixel_scale )
+
+ def dims(self, dims=None):
+ if dims is not None:
+ self.__dims = dims
+ return self.__dims
+
+ def processed_dims(self, processed_dims=None):
+ if processed_dims is not None:
+ self.__processed_dims = processed_dims
+ return self.__processed_dims
+
+ def name(self, name=None):
+ if name is not None:
+ self.__name = name
+ return self.__name
+
+ def pixel_scale(self, pixel_scale=None):
+ if pixel_scale is not None:
+ self.__pixel_scale = pixel_scale
+ return self.__pixel_scale
+
+class hirise_dtm_helper(object):
+ ''' methods to understand/import a HiRISE DTM formatted as a PDS .IMG '''
+
+ def __init__(self, context, filepath):
+ self.__context = context
+ self.__filepath = filepath
+ self.__ignore_value = 0x00000000
+ self.__bin_mode = 'BIN6'
+ self.scale( 1.0 )
+ self.__cropXY = False
+ self.marsRed(False)
+
+ def bin_mode(self, bin_mode=None):
+ if bin_mode != None:
+ self.__bin_mode = bin_mode
+ return self.__bin_mode
+
+ def scale(self, scale=None):
+ if scale is not None:
+ self.__scale = scale
+ return self.__scale
+
+ def crop(self, widthX, widthY, offX, offY):
+ self.__cropXY = [ widthX, widthY, offX, offY ]
+ return self.__cropXY
+
+ def marsRed(self, marsRed=None):
+ if marsRed is not None:
+ self.__marsRed = marsRed
+ return self.__marsRed
+
+ def dbg(self, mesg):
+ print(mesg)
+
+ ############################################################################
+ ## PDS Label Operations
+ ############################################################################
+
+ def parsePDSLabel(self, labelIter, currentObjectName=None, level = ""):
+ # Let's parse this thing... semi-recursively
+ ## I started writing this caring about everything in the PDS standard but ...
+ ## it's a mess and I only need a few things -- thar be hacks below
+ ## Mostly I just don't care about continued data from previous lines
+ label_structure = []
+
+ # When are we done with this level?
+ endStr = "END"
+ if not currentObjectName == None:
+ endStr = "END_OBJECT = %s" % currentObjectName
+ line = ""
+
+ while not line.rstrip() == endStr:
+ line = next(labelIter)
+
+ # Get rid of comments
+ comment = line.find("/*")
+ if comment > -1:
+ line = line[:comment]
+
+ # Take notice of objects
+ if line[:8] == "OBJECT =":
+ objName = line[8:].rstrip()
+ label_structure.append(
+ (
+ objName.lstrip().rstrip(),
+ self.parsePDSLabel(labelIter, objName.lstrip().rstrip(), level + " ")
+ )
+ )
+ elif line.find("END_OBJECT =") > -1:
+ pass
+ elif len(line.rstrip().lstrip()) > 0:
+ key_val = line.split(" = ", 2)
+ if len(key_val) == 2:
+ label_structure.append( (key_val[0].rstrip().lstrip(), key_val[1].rstrip().lstrip()) )
+
+ return label_structure
+
+ # There has got to be a better way in python?
+ def iterArr(self, label):
+ for line in label:
+ yield line
+
+ def getPDSLabel(self, img):
+ # Just takes file and stores it into an array for later use
+ label = []
+ done = False;
+ # Grab label into array of lines
+ while not done:
+ line = str(img.readline(), 'utf-8')
+ if line.rstrip() == "END":
+ done = True
+ label.append(line)
+ return (label, self.parsePDSLabel(self.iterArr(label)))
+
+ def getLinesAndSamples(self, label):
+ ''' uses the parsed PDS Label to get the LINES and LINE_SAMPLES parameters
+ from the first object named "IMAGE" -- is hackish
+ '''
+ lines = None
+ line_samples = None
+ for obj in label:
+ if obj[0] == "IMAGE":
+ return self.getLinesAndSamples(obj[1])
+ if obj[0] == "LINES":
+ lines = int(obj[1])
+ if obj[0] == "LINE_SAMPLES":
+ line_samples = int(obj[1])
+
+ return ( line_samples, lines )
+
+ def getValidMinMax(self, label):
+ ''' uses the parsed PDS Label to get the VALID_MINIMUM and VALID_MAXIMUM parameters
+ from the first object named "IMAGE" -- is hackish
+ '''
+ lines = None
+ line_samples = None
+ for obj in label:
+ if obj[0] == "IMAGE":
+ return self.getValidMinMax(obj[1])
+ if obj[0] == "VALID_MINIMUM":
+ vmin = float(obj[1])
+ if obj[0] == "VALID_MAXIMUM":
+ vmax = float(obj[1])
+
+ return ( vmin, vmax )
+
+ def getMissingConstant(self, label):
+ ''' uses the parsed PDS Label to get the MISSING_CONSTANT parameter
+ from the first object named "IMAGE" -- is hackish
+ '''
+
+ lines = None
+ line_samples = None
+ for obj in label:
+ if obj[0] == "IMAGE":
+ return self.getMissingConstant(obj[1])
+ if obj[0] == "MISSING_CONSTANT":
+ bit_string_repr = obj[1]
+
+ # This is always the same for a HiRISE image, so we are just checking it
+ # to be a little less insane here. If someone wants to support another
+ # constant then go for it. Just make sure this one continues to work too
+ pieces = bit_string_repr.split("#")
+ if pieces[0] == "16" and pieces[1] == "FF7FFFFB":
+ ignore_value = unpack("f", pack("I", 0xFF7FFFFB))[0]
+
+ return ( ignore_value )
+
+ ############################################################################
+ ## Image operations
+ ############################################################################
+
+ # decorator to run a generator in a thread
+ def threaded_generator(func):
+ def start(*args,**kwargs):
+ # Setup a queue of returned items
+ yield_q = queue.Queue()
+ # Thread to run generator inside of
+ def worker():
+ for obj in func(*args,**kwargs): yield_q.put(obj)
+ yield_q.put(StopIteration)
+ t = threading.Thread(target=worker)
+ t.start()
+ # yield from the queue as fast as we can
+ obj = yield_q.get()
+ while obj is not StopIteration:
+ yield obj
+ obj = yield_q.get()
+
+ # return the thread-wrapped generator
+ return start
+
+ @threaded_generator
+ def bin2(self, image_iter, bin2_method_type="SLOW"):
+ ''' this is an iterator that: Given an image iterator will yield binned lines '''
+
+ img_props = next(image_iter)
+ # dimensions shrink as we remove pixels
+ processed_dims = img_props.processed_dims()
+ processed_dims = ( processed_dims[0]//2, processed_dims[1]//2 )
+ img_props.processed_dims( processed_dims )
+ # each pixel is larger as binning gets larger
+ pixel_scale = img_props.pixel_scale()
+ pixel_scale = ( pixel_scale[0]*2, pixel_scale[1]*2 )
+ img_props.pixel_scale( pixel_scale )
+ yield img_props
+
+ # Take two lists [a1, a2, a3], [b1, b2, b3] and combine them into one
+ # list of [a1 + b1, a2+b2, ... ] as long as both values are not ignorable
+ combine_fun = lambda a, b: a != self.__ignore_value and b != self.__ignore_value and a + b or self.__ignore_value
+
+ line_count = 0
+ ret_list = []
+ for line in image_iter:
+ if line_count == 1:
+ line_count = 0
+ tmp_list = list(map(combine_fun, line, last_line))
+ while len(tmp_list) > 1:
+ ret_list.append( combine_fun( tmp_list[0], tmp_list[1] ) )
+ del tmp_list[0:2]
+ yield ret_list
+ ret_list = []
+ last_line = line
+ line_count += 1
+
+ @threaded_generator
+ def bin6(self, image_iter, bin6_method_type="SLOW"):
+ ''' this is an iterator that: Given an image iterator will yield binned lines '''
+
+ img_props = next(image_iter)
+ # dimensions shrink as we remove pixels
+ processed_dims = img_props.processed_dims()
+ processed_dims = ( processed_dims[0]//6, processed_dims[1]//6 )
+ img_props.processed_dims( processed_dims )
+ # each pixel is larger as binning gets larger
+ pixel_scale = img_props.pixel_scale()
+ pixel_scale = ( pixel_scale[0]*6, pixel_scale[1]*6 )
+ img_props.pixel_scale( pixel_scale )
+ yield img_props
+
+ if bin6_method_type == "FAST":
+ bin6_method = self.bin6_real_fast
+ else:
+ bin6_method = self.bin6_real
+
+ raw_data = []
+ line_count = 0
+ for line in image_iter:
+ raw_data.append( line )
+ line_count += 1
+ if line_count == 6:
+ yield bin6_method( raw_data )
+ line_count = 0
+ raw_data = []
+
+ def bin6_real(self, raw_data):
+ ''' does a 6x6 sample of raw_data and returns a single line of data '''
+ # TODO: make this more efficient
+
+ binned_data = []
+
+ # Filter out those unwanted hugely negative values...
+ filter_fun = lambda a: self.__ignore_value.__ne__(a)
+
+ base = 0
+ for i in range(0, len(raw_data[0])//6):
+
+ ints = list(filter( filter_fun, raw_data[0][base:base+6] +
+ raw_data[1][base:base+6] +
+ raw_data[2][base:base+6] +
+ raw_data[3][base:base+6] +
+ raw_data[4][base:base+6] +
+ raw_data[5][base:base+6] ))
+ len_ints = len( ints )
+
+ # If we have all pesky values, return a pesky value
+ if len_ints == 0:
+ binned_data.append( self.__ignore_value )
+ else:
+ binned_data.append( sum(ints) / len(ints) )
+
+ base += 6
+ return binned_data
+
+ def bin6_real_fast(self, raw_data):
+ ''' takes a single value from each 6x6 sample of raw_data and returns a single line of data '''
+ # TODO: make this more efficient
+
+ binned_data = []
+
+ base = 0
+ for i in range(0, len(raw_data[0])//6):
+ binned_data.append( raw_data[0][base] )
+ base += 6
+
+ return binned_data
+
+ @threaded_generator
+ def bin12(self, image_iter, bin12_method_type="SLOW"):
+ ''' this is an iterator that: Given an image iterator will yield binned lines '''
+
+ img_props = next(image_iter)
+ # dimensions shrink as we remove pixels
+ processed_dims = img_props.processed_dims()
+ processed_dims = ( processed_dims[0]//12, processed_dims[1]//12 )
+ img_props.processed_dims( processed_dims )
+ # each pixel is larger as binning gets larger
+ pixel_scale = img_props.pixel_scale()
+ pixel_scale = ( pixel_scale[0]*12, pixel_scale[1]*12 )
+ img_props.pixel_scale( pixel_scale )
+ yield img_props
+
+ if bin12_method_type == "FAST":
+ bin12_method = self.bin12_real_fast
+ else:
+ bin12_method = self.bin12_real
+
+ raw_data = []
+ line_count = 0
+ for line in image_iter:
+ raw_data.append( line )
+ line_count += 1
+ if line_count == 12:
+ yield bin12_method( raw_data )
+ line_count = 0
+ raw_data = []
+
+ def bin12_real(self, raw_data):
+ ''' does a 12x12 sample of raw_data and returns a single line of data '''
+
+ binned_data = []
+
+ # Filter out those unwanted hugely negative values...
+ filter_fun = lambda a: self.__ignore_value.__ne__(a)
+
+ base = 0
+ for i in range(0, len(raw_data[0])//12):
+
+ ints = list(filter( filter_fun, raw_data[0][base:base+12] +
+ raw_data[1][base:base+12] +
+ raw_data[2][base:base+12] +
+ raw_data[3][base:base+12] +
+ raw_data[4][base:base+12] +
+ raw_data[5][base:base+12] +
+ raw_data[6][base:base+12] +
+ raw_data[7][base:base+12] +
+ raw_data[8][base:base+12] +
+ raw_data[9][base:base+12] +
+ raw_data[10][base:base+12] +
+ raw_data[11][base:base+12] ))
+ len_ints = len( ints )
+
+ # If we have all pesky values, return a pesky value
+ if len_ints == 0:
+ binned_data.append( self.__ignore_value )
+ else:
+ binned_data.append( sum(ints) / len(ints) )
+
+ base += 12
+ return binned_data
+
+ def bin12_real_fast(self, raw_data):
+ ''' takes a single value from each 12x12 sample of raw_data and returns a single line of data '''
+ return raw_data[0][11::12]
+
+ @threaded_generator
+ def cropXY(self, image_iter, XSize=None, YSize=None, XOffset=0, YOffset=0):
+ ''' return a cropped portion of the image '''
+
+ img_props = next(image_iter)
+ # dimensions shrink as we remove pixels
+ processed_dims = img_props.processed_dims()
+
+ if XSize == None:
+ XSize = processed_dims[0]
+ if YSize == None:
+ YSize = processed_dims[1]
+
+ if XSize + XOffset > processed_dims[0]:
+ self.dbg("WARNING: Upstream dims are larger than cropped XSize dim")
+ XSize = processed_dims[0]
+ XOffset = 0
+ if YSize + YOffset > processed_dims[1]:
+ self.dbg("WARNING: Upstream dims are larger than cropped YSize dim")
+ YSize = processed_dims[1]
+ YOffset = 0
+
+ img_props.processed_dims( (XSize, YSize) )
+ yield img_props
+
+ currentY = 0
+ for line in image_iter:
+ if currentY >= YOffset and currentY <= YOffset + YSize:
+ yield line[XOffset:XOffset+XSize]
+ # Not much point in reading the rest of the data...
+ if currentY == YOffset + YSize:
+ return
+ currentY += 1
+
+ @threaded_generator
+ def getImage(self, img, img_props):
+ ''' Assumes 32-bit pixels -- bins image '''
+ dims = img_props.dims()
+ self.dbg("getting image (x,y): %d,%d" % ( dims[0], dims[1] ))
+
+ # setup to unpack more efficiently.
+ x_len = dims[0]
+ # little endian (PC_REAL)
+ unpack_str = "<"
+ # unpack_str = ">"
+ unpack_bytes_str = "<"
+ pack_bytes_str = "="
+ # 32 bits/sample * samples/line = y_bytes (per line)
+ x_bytes = 4*x_len
+ for x in range(0, x_len):
+ # 32-bit float is "d"
+ unpack_str += "f"
+ unpack_bytes_str += "I"
+ pack_bytes_str += "I"
+
+ # Each iterator yields this first ... it is for reference of the next iterator:
+ yield img_props
+
+ for y in range(0, dims[1]):
+ # pixels is a byte array
+ pixels = b''
+ while len(pixels) < x_bytes:
+ new_pixels = img.read( x_bytes - len(pixels) )
+ pixels += new_pixels
+ if len(new_pixels) == 0:
+ x_bytes = -1
+ pixels = []
+ self.dbg("Uh oh: unexpected EOF!")
+ if len(pixels) == x_bytes:
+ if 0 == 1:
+ repacked_pixels = b''
+ for integer in unpack(unpack_bytes_str, pixels):
+ repacked_pixels += pack("=I", integer)
+ yield unpack( unpack_str, repacked_pixels )
+ else:
+ yield unpack( unpack_str, pixels )
+
+ @threaded_generator
+ def shiftToOrigin(self, image_iter, image_min_max):
+ ''' takes a generator and shifts the points by the valid minimum
+ also removes points with value self.__ignore_value and replaces them with None
+ '''
+
+ # use the passed in values ...
+ valid_min = image_min_max[0]
+
+ # pass on dimensions/pixel_scale since we don't modify them here
+ yield next(image_iter)
+
+ self.dbg("shiftToOrigin filter enabled...");
+
+ # closures rock!
+ def normalize_fun(point):
+ if point == self.__ignore_value:
+ return None
+ return point - valid_min
+
+ for line in image_iter:
+ yield list(map(normalize_fun, line))
+ self.dbg("shifted all points")
+
+ @threaded_generator
+ def scaleZ(self, image_iter, scale_factor):
+ ''' scales the mesh values by a factor '''
+ # pass on dimensions since we don't modify them here
+ yield next(image_iter)
+
+ scale_factor = self.scale()
+
+ def scale_fun(point):
+ try:
+ return point * scale_factor
+ except:
+ return None
+
+ for line in image_iter:
+ yield list(map(scale_fun, line))
+
+ def genMesh(self, image_iter):
+ '''Returns a mesh object from an image iterator this has the
+ value-added feature that a value of "None" is ignored
+ '''
+
+ # Get the output image size given the above transforms
+ img_props = next(image_iter)
+
+ # Let's interpolate the binned DTM with blender -- yay meshes!
+ coords = []
+ faces = []
+ face_count = 0
+ coord = -1
+ max_x = img_props.processed_dims()[0]
+ max_y = img_props.processed_dims()[1]
+
+ scale_x = self.scale() * img_props.pixel_scale()[0]
+ scale_y = self.scale() * img_props.pixel_scale()[1]
+
+ line_count = 0
+ current_line = []
+ # seed the last line (or previous line) with a line
+ last_line = next(image_iter)
+ point_offset = 0
+ previous_point_offset = 0
+
+ # Let's add any initial points that are appropriate
+ x = 0
+ point_offset += len( last_line ) - last_line.count(None)
+ for z in last_line:
+ if z != None:
+ coords.extend([x*scale_x, 0.0, z])
+ coord += 1
+ x += 1
+
+ # We want to ignore points with a value of "None" but we also need to create vertices
+ # with an index that we can re-create on the next line. The solution is to remember
+ # two offsets: the point offset and the previous point offset.
+ # these offsets represent the point index that blender gets -- not the number of
+ # points we have read from the image
+
+ # if "x" represents points that are "None" valued then conceptually this is how we
+ # think of point indices:
+ #
+ # previous line: offset0 x x +1 +2 +3
+ # current line: offset1 x +1 +2 +3 x
+
+ # once we can map points we can worry about making triangular or square faces to fill
+ # the space between vertices so that blender is more efficient at managing the final
+ # structure.
+
+ self.dbg('generate mesh coords/faces from processed image data...')
+
+ # read each new line and generate coordinates+faces
+ for dtm_line in image_iter:
+
+ # Keep track of where we are in the image
+ line_count += 1
+ y_val = line_count*-scale_y
+ if line_count % 31 == 0:
+ self.dbg("reading image... %d of %d" % ( line_count, max_y ))
+
+ # Just add all points blindly
+ # TODO: turn this into a map
+ x = 0
+ for z in dtm_line:
+ if z != None:
+ coords.extend( [x*scale_x, y_val, z] )
+ coord += 1
+ x += 1
+
+ # Calculate faces
+ for x in range(0, max_x - 1):
+ vals = [
+ last_line[ x + 1 ],
+ last_line[ x ],
+ dtm_line[ x ],
+ dtm_line[ x + 1 ],
+ ]
+
+ # Two or more values of "None" means we can ignore this block
+ none_val = vals.count(None)
+
+ # Common case: we can create a square face
+ if none_val == 0:
+ faces.extend( [
+ previous_point_offset,
+ previous_point_offset+1,
+ point_offset+1,
+ point_offset,
+ ] )
+ face_count += 1
+ elif none_val == 1:
+ # special case: we can implement a triangular face
+ ## NB: blender 2.5 makes a triangular face when the last coord is 0
+ # TODO: implement a triangular face
+ pass
+
+ if vals[1] != None:
+ previous_point_offset += 1
+ if vals[2] != None:
+ point_offset += 1
+
+ # Squeeze the last point offset increment out of the previous line
+ if last_line[-1] != None:
+ previous_point_offset += 1
+
+ # Squeeze the last point out of the current line
+ if dtm_line[-1] != None:
+ point_offset += 1
+
+ # remember what we just saw (and forget anything before that)
+ last_line = dtm_line
+
+ self.dbg('generate mesh from coords/faces...')
+ me = bpy.data.meshes.new(img_props.name()) # create a new mesh
+
+ self.dbg('coord: %d' % coord)
+ self.dbg('len(coords): %d' % len(coords))
+ self.dbg('len(faces): %d' % len(faces))
+
+ self.dbg('setting coords...')
+ me.vertices.add(len(coords)/3)
+ me.vertices.foreach_set("co", coords)
+
+ self.dbg('setting faces...')
+ me.faces.add(len(faces)/4)
+ me.faces.foreach_set("vertices_raw", faces)
+
+ self.dbg('running update...')
+ me.update()
+
+ bin_desc = self.bin_mode()
+ if bin_desc == 'NONE':
+ bin_desc = 'No Bin'
+
+ ob=bpy.data.objects.new("DTM - %s" % bin_desc, me)
+
+ return ob
+
+ def marsRedMaterial(self):
+ ''' produce some approximation of a mars surface '''
+ mat = None
+ for material in bpy.data.materials:
+ if material.getName() == "redMars":
+ mat = material
+ if mat is None:
+ mat = bpy.data.materials.new("redMars")
+ mat.diffuse_shader = 'MINNAERT'
+ mat.setRGBCol( (0.426, 0.213, 0.136) )
+ mat.setDiffuseDarkness(0.8)
+ mat.specular_shader = 'WARDISO'
+ mat.setSpecCol( (1.000, 0.242, 0.010) )
+ mat.setSpec( 0.010 )
+ mat.setRms( 0.100 )
+ return mat
+
+ ################################################################################
+ # Yay, done with helper functions ... let's see the abstraction in action! #
+ ################################################################################
+ def execute(self):
+
+ self.dbg('opening/importing file: %s' % self.__filepath)
+ img = open(self.__filepath, 'rb')
+
+ self.dbg('read PDS Label...')
+ (label, parsedLabel) = self.getPDSLabel(img)
+
+ self.dbg('parse PDS Label...')
+ image_dims = self.getLinesAndSamples(parsedLabel)
+ img_min_max_vals = self.getValidMinMax(parsedLabel)
+ self.__ignore_value = self.getMissingConstant(parsedLabel)
+
+ self.dbg('import/bin image data...')
+
+ # MAGIC VALUE? -- need to formalize this to rid ourselves of bad points
+ img.seek(28)
+ # Crop off 4 lines
+ img.seek(4*image_dims[0])
+
+ # HiRISE images (and most others?) have 1m x 1m pixels
+ pixel_scale=(1, 1)
+
+ # The image we are importing
+ image_name = os.path.basename( self.__filepath )
+
+ # Set the properties of the image in a manageable object
+ img_props = image_props( image_name, image_dims, pixel_scale )
+
+ # Get an iterator to iterate over lines
+ image_iter = self.getImage(img, img_props)
+
+ ## Wrap the image_iter generator with other generators to modify the dtm on a
+ ## line-by-line basis. This creates a stream of modifications instead of reading
+ ## all of the data at once, processing all of the data (potentially several times)
+ ## and then handing it off to blender
+ ## TODO: find a way to alter projection based on transformations below
+
+ if self.__cropXY:
+ image_iter = self.cropXY(image_iter,
+ XSize=self.__cropXY[0],
+ YSize=self.__cropXY[1],
+ XOffset=self.__cropXY[2],
+ YOffset=self.__cropXY[3]
+ )
+
+ # Select an appropriate binning mode
+ ## TODO: generalize the binning fn's
+ bin_mode = self.bin_mode()
+ bin_mode_funcs = {
+ 'BIN2': self.bin2(image_iter),
+ 'BIN6': self.bin6(image_iter),
+ 'BIN6-FAST': self.bin6(image_iter, 'FAST'),
+ 'BIN12': self.bin12(image_iter),
+ 'BIN12-FAST': self.bin12(image_iter, 'FAST')
+ }
+ if bin_mode in bin_mode_funcs.keys():
+ image_iter = bin_mode_funcs[ bin_mode ]
+
+ image_iter = self.shiftToOrigin(image_iter, img_min_max_vals)
+
+ if self.scale != 1.0:
+ image_iter = self.scaleZ(image_iter, img_min_max_vals)
+
+ # Create a new mesh object and set data from the image iterator
+ self.dbg('generating mesh object...')
+ ob_new = self.genMesh(image_iter)
+
+ if self.marsRed():
+ mars_red = self.marsRedMaterial()
+ ob_new.materials += [mars_red]
+
+ if img:
+ img.close()
+
+ # Add mesh object to the current scene
+ scene = self.__context.scene
+ self.dbg('linking object to scene...')
+ scene.objects.link(ob_new)
+ scene.update()
+
+ # deselect other objects
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # scene.objects.active = ob_new
+ # Select the new mesh
+ ob_new.select = True
+
+ self.dbg('done with ops ... now wait for blender ...')
+
+ return ('FINISHED',)
+
+def load(operator, context, filepath, scale, bin_mode, cropVars, marsRed):
+ print("Bin Mode: %s" % bin_mode)
+ print("Scale: %f" % scale)
+ helper = hirise_dtm_helper(context,filepath)
+ helper.bin_mode( bin_mode )
+ helper.scale( scale )
+ if cropVars:
+ helper.crop( cropVars[0], cropVars[1], cropVars[2], cropVars[3] )
+ helper.execute()
+ if marsRed:
+ helper.marsRed(marsRed)
+
+ print("Loading %s" % filepath)
+ return {'FINISHED'}
diff --git a/io_export_directx_x.py b/io_export_directx_x.py
new file mode 100644
index 00000000..76f62d42
--- /dev/null
+++ b/io_export_directx_x.py
@@ -0,0 +1,1229 @@
+# ***** 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": (2, 0),
+ "blender": (2, 5, 5),
+ "api": 33427,
+ "location": "File > Export",
+ "description": "Export to the DirectX Model Format (.x)",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/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...")
+ Config.File = open(Config.FilePath, "w")
+ if Config.Verbose:
+ print("Done")
+
+ if Config.Verbose:
+ print("Generating Object list for export...")
+ if Config.ExportMode == 1:
+ Config.ExportList = [Object for Object in Config.context.scene.objects
+ if Object.type in ("ARMATURE", "EMPTY", "MESH")
+ and Object.parent is 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...")
+ Config.SystemMatrix = Matrix()
+ if Config.RotateX:
+ Config.SystemMatrix *= Matrix.Rotation(radians(-90), 4, "X")
+ if Config.CoordinateSystem == 1:
+ Config.SystemMatrix *= Matrix.Scale(-1, 4, Vector((0, 1, 0)))
+
+ 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...")
+ WriteHeader(Config)
+ if Config.Verbose:
+ print("Done")
+
+ Config.Whitespace = 0
+ if Config.Verbose:
+ print("Writing Root Frame...")
+ WriteRootFrame(Config)
+ if Config.Verbose:
+ print("Done")
+
+ Config.ObjectList = []
+ if Config.Verbose:
+ print("Writing Objects...")
+ WriteObjects(Config, Config.ExportList)
+ if Config.Verbose:
+ print("Done")
+
+ Config.Whitespace -= 1
+ Config.File.write("{}}} //End of Root Frame\n".format(" " * Config.Whitespace))
+
+ if Config.ExportAnimation:
+ if Config.IncludeFrameRate:
+ if Config.Verbose:
+ print("Writing Frame Rate...")
+ 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.vertices)
+ 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 WriteRootFrame(Config):
+ Config.File.write("{}Frame Root {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+
+ Config.File.write("{}FrameTransformMatrix {{\n".format(" " * Config.Whitespace))
+ Config.Whitespace += 1
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, Config.SystemMatrix[0][0], Config.SystemMatrix[0][1], Config.SystemMatrix[0][2], Config.SystemMatrix[0][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, Config.SystemMatrix[1][0], Config.SystemMatrix[1][1], Config.SystemMatrix[1][2], Config.SystemMatrix[1][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f},\n".format(" " * Config.Whitespace, Config.SystemMatrix[2][0], Config.SystemMatrix[2][1], Config.SystemMatrix[2][2], Config.SystemMatrix[2][3]))
+ Config.File.write("{}{:9f},{:9f},{:9f},{:9f};;\n".format(" " * Config.Whitespace, Config.SystemMatrix[3][0], Config.SystemMatrix[3][1], Config.SystemMatrix[3][2], Config.SystemMatrix[3][3]))
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+
+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...")
+ 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 is 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...")
+ 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 = Object.matrix_local
+
+ 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))
+ 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.copy().invert()
+ else:
+ BoneMatrix = Matrix()
+
+ BoneMatrix *= PoseBone.matrix
+
+ 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...")
+ WriteMeshVertices(Config, Mesh)
+ if Config.Verbose:
+ print(" Done\n Writing Mesh Normals...")
+ WriteMeshNormals(Config, Mesh)
+ if Config.Verbose:
+ print(" Done\n Writing Mesh Materials...")
+ WriteMeshMaterials(Config, Mesh)
+ if Config.Verbose:
+ print(" Done")
+ if Mesh.uv_textures:
+ if Config.Verbose:
+ print(" Writing Mesh UV Coordinates...")
+ WriteMeshUVCoordinates(Config, Mesh)
+ if Config.Verbose:
+ print(" Done")
+ if Config.ExportArmatures:
+ if Config.Verbose:
+ print(" Writing Mesh Skin Weights...")
+ 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.vertices)
+
+ if Config.CoordinateSystem == 1:
+ Vertices = Vertices[::-1]
+ for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
+ Position = 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.vertices)))
+ for Vertex in Face.vertices:
+ 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.vertices)
+
+ if Config.CoordinateSystem == 1:
+ Vertices = Vertices[::-1]
+ for Vertex in [Mesh.vertices[Vertex] for Vertex in Vertices]:
+ if Face.use_smooth:
+ Normal = Vertex.normal
+ else:
+ Normal = 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.vertices)))
+ for Vertex in Face.vertices:
+ 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.vertices:
+ #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.vertices:
+ 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.vertices)
+ 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.vertices[Vertex].groups) if Object.vertex_groups[Group.group].name in PoseBones}
+
+ WeightTotal = 0.0
+ for Weight in [Group.weight for Group in Mesh.vertices[Vertex].groups if Object.vertex_groups[Group.group].name in PoseBones]:
+ WeightTotal += Weight
+
+ if WeightTotal:
+ VertexWeights.append(Mesh.vertices[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.copy().invert()
+ BoneMatrix *= ArmatureObject.matrix_world.copy().invert()
+ BoneMatrix *= Object.matrix_world
+
+ 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...")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(PositionFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ if Keyframe.co[0] < bpy.context.scene.frame_start:
+ AllKeyframes.add(bpy.context.scene.frame_start)
+ elif Keyframe.co[0] > bpy.context.scene.frame_end:
+ AllKeyframes.add(bpy.context.scene.frame_end)
+ else:
+ 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.frame_set(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])
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
+ if Keyframe == AllKeyframes[-1]:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+
+ else:
+ Config.File.write("{}2;\n{}1;\n".format(" " * Config.Whitespace, " " * Config.Whitespace))
+ bpy.context.scene.frame_set(bpy.context.scene.frame_start)
+ Position = Object.matrix_local.translation_part()
+ 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...")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(RotationFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ if Keyframe.co[0] < bpy.context.scene.frame_start:
+ AllKeyframes.add(bpy.context.scene.frame_start)
+ elif Keyframe.co[0] > bpy.context.scene.frame_end:
+ AllKeyframes.add(bpy.context.scene.frame_end)
+ else:
+ 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.frame_set(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 = Rotation.to_quat()
+ Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";4;").ljust(8), - Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
+ if Keyframe == AllKeyframes[-1]:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ else:
+ Config.File.write("{}0;\n{}1;\n".format(" " * Config.Whitespace, " " * Config.Whitespace))
+ bpy.context.scene.frame_set(bpy.context.scene.frame_start)
+ Rotation = Object.rotation_euler.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...")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(ScaleFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ if Keyframe.co[0] < bpy.context.scene.frame_start:
+ AllKeyframes.add(bpy.context.scene.frame_start)
+ elif Keyframe.co[0] > bpy.context.scene.frame_end:
+ AllKeyframes.add(bpy.context.scene.frame_end)
+ else:
+ 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.frame_set(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])
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
+ if Keyframe == AllKeyframes[-1]:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ else:
+ Config.File.write("{}1;\n{}1;\n".format(" " * Config.Whitespace, " " * Config.Whitespace))
+ bpy.context.scene.frame_set(bpy.context.scene.frame_start)
+ Scale = Object.matrix_local.scale_part()
+ 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...")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(PositionFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ if Keyframe.co[0] < bpy.context.scene.frame_start:
+ AllKeyframes.add(bpy.context.scene.frame_start)
+ elif Keyframe.co[0] > bpy.context.scene.frame_end:
+ AllKeyframes.add(bpy.context.scene.frame_end)
+ else:
+ 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.frame_set(Keyframe)
+
+ if Bone.parent:
+ PoseMatrix = Bone.parent.matrix.copy().invert()
+ else:
+ PoseMatrix = Matrix()
+ PoseMatrix *= Bone.matrix
+
+ Position = PoseMatrix.translation_part()
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
+ if Keyframe == AllKeyframes[-1]:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print(" Done")
+
+ #Rotation
+ if Config.Verbose:
+ print(" Writing Rotation...")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(RotationFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ if Keyframe.co[0] < bpy.context.scene.frame_start:
+ AllKeyframes.add(bpy.context.scene.frame_start)
+ elif Keyframe.co[0] > bpy.context.scene.frame_end:
+ AllKeyframes.add(bpy.context.scene.frame_end)
+ else:
+ 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.frame_set(Keyframe)
+
+ if Bone.parent:
+ PoseMatrix = Bone.parent.matrix.copy().invert()
+ else:
+ PoseMatrix = Matrix()
+ PoseMatrix *= Bone.matrix
+
+ Rotation = PoseMatrix.rotation_part().to_quat()
+ Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";4;").ljust(8), -Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
+ if Keyframe == AllKeyframes[-1]:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print(" Done")
+
+ #Scale
+ if Config.Verbose:
+ print(" Writing Scale...")
+ AllKeyframes = set()
+ for Index, FCurve in enumerate(ScaleFCurves):
+ if FCurve:
+ Keyframes = []
+ for Keyframe in FCurve.keyframe_points:
+ if Keyframe.co[0] < bpy.context.scene.frame_start:
+ AllKeyframes.add(bpy.context.scene.frame_start)
+ elif Keyframe.co[0] > bpy.context.scene.frame_end:
+ AllKeyframes.add(bpy.context.scene.frame_end)
+ else:
+ 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.frame_set(Keyframe)
+
+ if Bone.parent:
+ PoseMatrix = Bone.parent.matrix.copy().invert()
+ else:
+ PoseMatrix = Matrix()
+ PoseMatrix *= Bone.matrix
+
+ Scale = PoseMatrix.scale_part()
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Keyframe - bpy.context.scene.frame_start) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
+ if Keyframe == AllKeyframes[-1]:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ 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...")
+ 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.frame_set(Frame + bpy.context.scene.frame_start)
+ Position = Object.matrix_local.translation_part()
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
+ if Frame == KeyframeCount-1:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print(" Done")
+
+ #Rotation
+ if Config.Verbose:
+ print(" Writing Rotation...")
+ 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.frame_set(Frame + bpy.context.scene.frame_start)
+ Rotation = Object.rotation_euler.to_quat()
+ Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";4;").ljust(8), -Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
+ if Frame == KeyframeCount-1:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print(" Done")
+
+ #Scale
+ if Config.Verbose:
+ print(" Writing Scale...")
+ 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.frame_set(Frame + bpy.context.scene.frame_start)
+ Scale = Object.matrix_local.scale_part()
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
+ if Frame == KeyframeCount-1:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ 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(" Writing Armature Bone Animation Data...")
+ PoseBones = Object.pose.bones
+ Bones = Object.data.bones
+ for Bone in PoseBones:
+ if Config.Verbose:
+ print(" Writing Bone: {}...".format(Bone.name))
+
+ 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...")
+ 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.frame_set(Frame + bpy.context.scene.frame_start)
+
+ if Bone.parent:
+ PoseMatrix = Bone.parent.matrix.copy().invert()
+ else:
+ PoseMatrix = Matrix()
+ PoseMatrix *= Bone.matrix
+
+ Position = PoseMatrix.translation_part()
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Position[0], Position[1], Position[2]))
+ if Frame == KeyframeCount-1:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print(" Done")
+
+ #Rotation
+ if Config.Verbose:
+ print(" Writing Rotation...")
+ 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.frame_set(Frame + bpy.context.scene.frame_start)
+
+ Rotation = Bones[Bone.name].matrix.to_quat() * Bone.rotation_quaternion
+
+ Config.File.write("{}{}{:9f},{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";4;").ljust(8), -Rotation[0], Rotation[1], Rotation[2], Rotation[3]))
+ if Frame == KeyframeCount-1:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ Config.Whitespace -= 1
+ Config.File.write("{}}}\n".format(" " * Config.Whitespace))
+ if Config.Verbose:
+ print(" Done")
+
+ #Scale
+ if Config.Verbose:
+ print(" Writing Scale...")
+ Config.File.write("{}AnimationKey {{ //Scale\n".format(" " * Config.Whitespace, KeyframeCount))
+ Config.Whitespace += 1
+ Config.File.write("{}1;\n{}{};\n".format(" " * Config.Whitespace, " " * Config.Whitespace, KeyframeCount))
+ for Frame in range(0, KeyframeCount):
+ bpy.context.scene.frame_set(Frame + bpy.context.scene.frame_start)
+
+ if Bone.parent:
+ PoseMatrix = Bone.parent.matrix.copy().invert()
+ else:
+ PoseMatrix = Matrix()
+ PoseMatrix *= Bone.matrix
+
+ Scale = PoseMatrix.scale_part()
+ Config.File.write("{}{}{:9f},{:9f},{:9f};;".format(" " * Config.Whitespace, (str(Frame) + ";3;").ljust(8), Scale[0], Scale[1], Scale[2]))
+ if Frame == KeyframeCount-1:
+ Config.File.write(";\n")
+ else:
+ Config.File.write(",\n")
+ 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 CloseFile(Config):
+ if Config.Verbose:
+ print("Closing File...")
+ 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(subtype='FILE_PATH')
+
+ #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.filepath
+ if not FilePath.lower().endswith(".x"):
+ FilePath += ".x"
+
+ Config = DirectXExporterSettings(context,
+ FilePath,
+ CoordinateSystem=self.CoordinateSystem,
+ RotateX=self.RotateX,
+ FlipNormals=self.FlipNormals,
+ ApplyModifiers=self.ApplyModifiers,
+ IncludeFrameRate=self.IncludeFrameRate,
+ ExportTextures=self.ExportTextures,
+ ExportArmatures=self.ExportArmatures,
+ ExportAnimation=self.ExportAnimation,
+ ExportMode=self.ExportMode,
+ Verbose=self.Verbose)
+ ExportDirectX(Config)
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ WindowManager = context.window_manager
+ WindowManager.fileselect_add(self)
+ return {"RUNNING_MODAL"}
+
+
+def menu_func(self, context):
+ default_path = os.path.splitext(bpy.data.filepath)[0] + ".x"
+ self.layout.operator(DirectXExporter.bl_idname, text="DirectX (.x)").filepath = default_path
+
+
+def register():
+ bpy.types.INFO_MT_file_export.append(menu_func)
+
+
+def unregister():
+ bpy.types.INFO_MT_file_export.remove(menu_func)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/io_export_pc2.py b/io_export_pc2.py
new file mode 100644
index 00000000..417b7e8a
--- /dev/null
+++ b/io_export_pc2.py
@@ -0,0 +1,199 @@
+# ##### 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": "Export Pointcache (.pc2)",
+ "author": "Florian Meyer (tstscr)",
+ "version": (1, 0),
+ "blender": (2, 5, 4),
+ "api": 33047,
+ "location": "File > Export",
+ "description": "Export Mesh Pointcache to .pc2",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/PC2_Pointcache_export",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=24703&group_id=153&atid=468",
+ "category": "Import-Export"}
+
+'''
+Usage Notes:
+
+in Maya Mel:
+cacheFile -pc2 1 -pcf "<insert filepath of source>" -f "<insert target filename w/o extension>" -dir "<insert directory path for target>" -format "OneFile";
+
+'''
+
+import bpy
+from bpy.props import *
+import mathutils, math, struct
+from os import remove
+import time
+from io_utils import ExportHelper
+
+def getSampling(start, end, sampling):
+ samples = [start - sampling
+ + x * sampling
+ for x in range(start, int((end-start)*1/sampling)+1)]
+ return samples
+
+def do_export(context, props, filepath):
+ mat_x90 = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
+ ob = context.active_object
+ sc = context.scene
+ start = props.range_start
+ end = props.range_end
+ sampling = float(props.sampling)
+ apply_modifiers = props.apply_modifiers
+ me = ob.create_mesh(sc, apply_modifiers, 'PREVIEW')
+ vertCount = len(me.vertices)
+ sampletimes = getSampling(start, end, sampling)
+ sampleCount = len(sampletimes)
+
+ # Create the header
+ headerFormat='<12ciiffi'
+ headerStr = struct.pack(headerFormat, 'P','O','I','N','T','C','A','C','H','E','2','\0',
+ 1, vertCount, start, sampling, sampleCount)
+
+ file = open(filepath, "wb")
+ file.write(headerStr)
+
+ for frame in sampletimes:
+ sc.frame_set(frame)
+ me = ob.create_mesh(sc, apply_modifiers, 'PREVIEW')
+
+ if len(me.vertices) != vertCount:
+ file.close()
+ try:
+ remove(filepath)
+ except:
+ empty = open(filepath, 'w')
+ empty.write('DUMMIFILE - export failed\n')
+ empty.close()
+ print('Export failed. Vertexcount of Object is not constant')
+ return False
+
+ if props.world_space:
+ me.transform(ob.matrix_world)
+ if props.rot_x90:
+ me.transform(mat_x90)
+
+ for v in me.vertices:
+ thisVertex = struct.pack('<fff', float(v.co[0]),
+ float(v.co[1]),
+ float(v.co[2]))
+ file.write(thisVertex)
+
+ file.flush()
+ file.close()
+ return True
+
+
+###### EXPORT OPERATOR #######
+class Export_pc2(bpy.types.Operator, ExportHelper):
+ '''Exports the active Object as a .pc2 Pointcache file.'''
+ bl_idname = "export_pc2"
+ bl_label = "Export Pointcache (.pc2)"
+
+ filename_ext = ".pc2"
+
+ rot_x90 = BoolProperty(name="Convert to Y-up",
+ description="Rotate 90 degrees around X to convert to y-up",
+ default=True)
+ world_space = BoolProperty(name="Export into Worldspace",
+ description="Transform the Vertexcoordinates into Worldspace",
+ default=False)
+ apply_modifiers = BoolProperty(name="Apply Modifiers",
+ description="Applies the Modifiers",
+ default=True)
+ range_start = IntProperty(name='Start Frame',
+ description='First frame to use for Export',
+ default=1)
+ range_end = IntProperty(name='End Frame',
+ description='Last frame to use for Export',
+ default=250)
+ sampling = EnumProperty(name='Sampling',
+ description='Sampling --> frames per sample (0.1 yields 10 samples per frame)',
+ items=[
+ ('0.01', '0.01', ''),
+ ('0.05', '0.05', ''),
+ ('0.1', '0.1', ''),
+ ('0.2', '0.2', ''),
+ ('0.25', '0.25', ''),
+ ('0.5', '0.5', ''),
+ ('1', '1', ''),
+ ('2', '2', ''),
+ ('3', '3', ''),
+ ('4', '4', ''),
+ ('5', '5', ''),
+ ('10', '10', '')],
+ default='1')
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object.type in ['MESH', 'CURVE', 'SURFACE', 'FONT']
+
+ def execute(self, context):
+ start_time = time.time()
+ print('\n_____START_____')
+ props = self.properties
+ filepath = self.filepath
+ filepath = bpy.path.ensure_ext(filepath, self.filename_ext)
+
+ exported = do_export(context, props, filepath)
+
+ if exported:
+ print('finished export in %s seconds' %((time.time() - start_time)))
+ print(filepath)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+
+ if True:
+ # File selector
+ wm.fileselect_add(self) # will run self.execute()
+ return {'RUNNING_MODAL'}
+ elif True:
+ # search the enum
+ wm.invoke_search_popup(self)
+ return {'RUNNING_MODAL'}
+ elif False:
+ # Redo popup
+ return wm.invoke_props_popup(self, event) #
+ elif False:
+ return self.execute(context)
+
+
+### REGISTER ###
+
+def menu_func(self, context):
+ self.layout.operator(Export_pc2.bl_idname, text="Pointcache (.pc2)")
+
+
+def register():
+ bpy.types.INFO_MT_file_export.append(menu_func)
+ #bpy.types.VIEW3D_PT_tools_objectmode.prepend(menu_func)
+
+def unregister():
+ bpy.types.INFO_MT_file_export.remove(menu_func)
+ #bpy.types.VIEW3D_PT_tools_objectmode.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/io_export_unreal_psk_psa.py b/io_export_unreal_psk_psa.py
new file mode 100644
index 00000000..b5f3773a
--- /dev/null
+++ b/io_export_unreal_psk_psa.py
@@ -0,0 +1,1784 @@
+# ***** 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 Skeleletal Mesh/Animation Data",
+ "author": "Darknet/Optimus_P-Fat/Active_Trash/Sinsoft",
+ "version": (2, 0),
+ "blender": (2, 5, 3),
+ "api": 31847,
+ "location": "File > Export > Skeletal Mesh/Animation Data (.psk/.psa)",
+ "description": "Export Unreal Engine (.psk)",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/Unreal_psk_psa",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=21366&group_id=153&atid=469",
+ "category": "Import-Export"}
+
+"""
+-- 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
+"""
+
+import os
+import time
+import datetime
+import bpy
+import mathutils
+import operator
+
+from struct import pack, calcsize
+
+
+# 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.vertices[0]
+ v1 = blender_face.vertices[1]
+ v2 = blender_face.vertices[2]
+
+ return (mesh.vertices[v0].co == mesh.vertices[v1].co or \
+ mesh.vertices[v1].co == mesh.vertices[v2].co or \
+ mesh.vertices[v2].co == mesh.vertices[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.vertices) > 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:
+ bpy.context.scene.unrealtriangulatebool = False
+ 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 is 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
+ bpy.ops.object.mode_set(mode='EDIT')
+ 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.vertices) != 3:
+ raise RuntimeError("Non-triangular face (%i)" % len(current_face.vertices))
+
+ #No Triangulate Yet
+ # if len(current_face.vertices) != 3:
+ # raise RuntimeError("Non-triangular face (%i)" % len(current_face.vertices))
+ # #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.uv_textures.active.data[face_index]#UVs for current face
+ #faceUV = current_mesh.uv_textures.active.data[0]#UVs for current face
+ #print(face_index,"<[FACE NUMBER")
+ uv_layer = current_mesh.uv_textures.active
+ 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.vertices[i]
+ vert = current_mesh.vertices[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.vertices[0];
+ dindex1 = current_face.vertices[1];
+ dindex2 = current_face.vertices[2];
+
+ current_mesh.vertices[dindex0].select = True
+ current_mesh.vertices[dindex1].select = True
+ current_mesh.vertices[dindex2].select = True
+
+ raise RuntimeError("normal vector coplanar with face! points:", current_mesh.vertices[dindex0].co, current_mesh.vertices[dindex1].co, current_mesh.vertices[dindex2].co)
+ #print(dir(current_face))
+ current_face.select = True
+ #print((current_face.use_smooth))
+ #not sure if this right
+ #tri.SmoothingGroups
+ if current_face.use_smooth == True:
+ tri.SmoothingGroups = 1
+ else:
+ tri.SmoothingGroups = 0
+
+ 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.vertices:
+ #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 is 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.PointIndex = point_index
+ #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 is 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 is 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 is 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 -----')
+ render_data = blender_scene.render
+ bHaveAction = True
+
+ anim_rate = render_data.fps
+
+ 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
+
+ if bpy.context.scene.unrealactionexportall :#if exporting all actions is ture then go do some work.
+ print("Exporting all action:",bpy.context.scene.unrealactionexportall)
+ print("[==== Action list Start====]")
+
+ print("Number of Action set(s):",len(bpy.data.actions))
+
+ for action in bpy.data.actions:#current number action sets
+ print("========>>>>>")
+ print("Action Name:",action.name)
+ #print("Groups:")
+ #for bone in action.groups:
+ #print("> Name: ",bone.name)
+ #print(dir(bone))
+
+ print("[==== Action list End ====]")
+
+ amatureobject = None #this is the armature set to none
+ bonenames = [] #bone name of the armature bones list
+
+ for arm in blender_armatures:
+ amatureobject = arm
+
+ print("\n[==== Armature Object ====]")
+ if amatureobject != None:
+ print("Name:",amatureobject.name)
+ print("Number of bones:", len(amatureobject.pose.bones))
+ for bone in amatureobject.pose.bones:
+ bonenames.append(bone.name)
+ print("[=========================]")
+
+ for ActionNLA in bpy.data.actions:
+ print("\n==== Action Set ====")
+ nobone = 0
+ baction = True
+ #print("\nChecking actions matching groups with bone names...")
+ #Check if the bone names matches the action groups names
+ for group in ActionNLA.groups:
+ for abone in bonenames:
+ #print("name:>>",abone)
+ if abone == group.name:
+ nobone += 1
+ break
+ #if action groups matches the bones length and names matching the gourps do something
+ if (len(ActionNLA.groups) == len(bonenames)) and (nobone == len(ActionNLA.groups)):
+ print("Action Set match: Pass")
+ baction = True
+ else:
+ print("Action Set match: Fail")
+ print("Action Name:",ActionNLA.name)
+ baction = False
+
+ if baction == True:
+ arm = amatureobject #set armature object
+ 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
+ arm.animation_data.action = ActionNLA
+ act = arm.animation_data.action
+ 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("Action Name:",action_name)
+ #look for min and max frame that current set keys
+ framemin, framemax = act.frame_range
+ #print("max frame:",framemax)
+ start_frame = int(framemin)
+ end_frame = int(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.frame_set(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) ====")
+ else:
+ print("Exporting one action:",bpy.context.scene.unrealactionexportall)
+ #list of armature objects
+ for arm in blender_armatures:
+ #check if there animation data from armature or something
+
+ 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.frame_range
+ #print("max frame:",framemax)
+ start_frame = int(framemin)
+ end_frame = int(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.frame_set(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):
+ #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.frame_set(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.frame_set(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.frame_set(cur_frame) #set frame back to original frame
+ print ("Exception during Animation Parse")
+ raise
+
+ # reset current frame
+
+ context.scene.frame_set(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))
+ print( "Current Script version: ",bl_addon_info['version'])
+ #MSG BOX EXPORT COMPLETE
+ #...
+
+ #DONE
+ print ("PSK/PSA Export Complete")
+
+def write_data(path, context):
+ print("//============================")
+ print("// running psk/psa export...")
+ print("//============================")
+ fs_callback(path, context)
+ 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"))
+
+bpy.types.Scene.unrealfpsrate = IntProperty(
+ name="fps rate",
+ description="Set the frame per second (fps) for unreal.",
+ default=24,min=1,max=100)
+
+bpy.types.Scene.unrealexport_settings = EnumProperty(
+ name="Export:",
+ description="Select a export settings (psk/psa/all)...",
+ items = exporttypedata, default = '0')
+
+bpy.types.Scene.unrealtriangulatebool = BoolProperty(
+ name="Triangulate Mesh",
+ description="Convert Quad to Tri Mesh Boolean...",
+ default=False)
+
+bpy.types.Scene.unrealactionexportall = BoolProperty(
+ name="All Actions",
+ description="This let you export all the actions from current armature that matches bone name in action groups names.",
+ default=False)
+
+bpy.types.Scene.unrealexportpsk = BoolProperty(
+ name="bool export psa",
+ description="bool for exporting this psk format",
+ default=True)
+
+bpy.types.Scene.unrealexportpsa = BoolProperty(
+ name="bool export psa",
+ description="bool for exporting this psa format",
+ default=True)
+
+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= "", subtype='FILE_PATH')
+ 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)
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ #check if skeleton mesh is needed to be exported
+ if (self.pskexportbool):
+ bpy.context.scene.unrealexportpsk = True
+ else:
+ bpy.context.scene.unrealexportpsk = False
+ #check if animation data is needed to be exported
+ if (self.psaexportbool):
+ bpy.context.scene.unrealexportpsa = True
+ else:
+ bpy.context.scene.unrealexportpsa = False
+
+ if (self.actionexportall):
+ bpy.context.scene.unrealactionexportall = True
+ else:
+ bpy.context.scene.unrealactionexportall = False
+
+ write_data(self.filepath, context)
+
+ self.report({'WARNING', 'INFO'}, exportmessage)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ wm.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+class VIEW3D_PT_unrealtools_objectmode(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "Unreal Tools"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object
+
+ def draw(self, context):
+ layout = self.layout
+ rd = context.scene
+ layout.prop(rd, "unrealexport_settings",expand=True)
+ layout.operator("object.UnrealExport")#button
+ #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.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):
+ 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)
+
+ #self.report({'WARNING', 'INFO'}, exportmessage)
+ self.report({'INFO'}, exportmessage)
+ return{'FINISHED'}
+
+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
+
+def register():
+ bpy.types.INFO_MT_file_export.append(menu_func)
+
+def unregister():
+ bpy.types.INFO_MT_file_export.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
diff --git a/io_import_gimp_image_to_scene.py b/io_import_gimp_image_to_scene.py
new file mode 100644
index 00000000..32b57b85
--- /dev/null
+++ b/io_import_gimp_image_to_scene.py
@@ -0,0 +1,670 @@
+# ##### 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 GIMP Image to Scene (.xcf, .xjt)",
+ "author": "Daniel Salazar (ZanQdo)",
+ "version": (2, 0, 0),
+ "blender": (2, 5, 5),
+ "api": 33419,
+ "location": "File > Import > GIMP Image to Scene(.xcf, .xjt)",
+ "description": "Imports GIMP multilayer image files into 3D Layers",
+ "warning": "XCF import requires xcftools installed",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/GIMPImageToScene",
+ "tracker_url": "http://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=25136&group_id=153&atid=469",
+ "category": "Import-Export"}
+
+"""
+This script imports GIMP layered image files into 3D Scenes (.xcf, .xjt)
+"""
+
+def main(File, Path, LayerViewers, MixerViewers, LayerOffset,\
+ LayerScale, OpacityMode, PremulAlpha, ShadelessMats,\
+ SetCamera, SetupCompo, GroupUntagged, Ext):
+
+ #-------------------------------------------------
+
+ #Folder = '['+File.rstrip(Ext)+']'+'_images/'
+ Folder = 'images_'+'['+File.rstrip(Ext)+']/'
+
+ if bpy.data.is_dirty:
+ PathSaveRaw = Path+Folder
+ PathSave = PathSaveRaw.replace(' ', '\ ')
+ try: os.mkdir(PathSaveRaw)
+ except: pass
+ else:
+ PathSave = bpy.data.filepath
+ RSlash = PathSave.rfind('/')
+ PathSaveRaw = PathSave[:RSlash+1]+Folder
+ PathSave = PathSaveRaw.replace(' ', '\ ')
+ try: os.mkdir(PathSaveRaw)
+ except: pass
+ PathSaveRaw = bpy.path.relpath(PathSaveRaw)+'/'
+
+ PathRaw = Path
+ Path = Path.replace(' ', '\ ')
+ if Ext == '.xjt':
+ ExtSave = '.jpg'
+ #-------------------------------------------------
+ # EXTRACT XJT
+ import tarfile
+
+ IMG = tarfile.open ('%s%s' % (PathRaw, File))
+ PRP = IMG.extractfile('PRP')
+
+ Members = IMG.getmembers()
+
+ for Member in Members:
+ Name = Member.name
+ if Name.startswith('l') and Name.endswith('.jpg'):
+ IMG.extract(Name, path=PathSaveRaw)
+
+ #-------------------------------------------------
+ # INFO XJT
+ IMGs = []
+ for Line in PRP.readlines():
+ Line = str(Line)
+
+ if Line.startswith("b'GIMP_XJ_IMAGE"):
+ for Segment in Line.split():
+ if Segment.startswith('w/h:'):
+ ResX, ResY = map (int, Segment[4:].split(','))
+ if Line.startswith("b'L") or Line.startswith("b'l"):
+
+ if Line.startswith("b'L"): HasAlpha = True
+ else: HasAlpha = False
+
+ md = None
+ op = 1
+ ox, oy = 0,0
+
+ for Segment in Line.split():
+
+ if Segment.startswith("b'"):
+ imageFile = 'l' + Segment[3:] + '.jpg'
+ imageFileAlpha ='la'+Segment[3:]+'.jpg'
+
+ # Get Widht and Height from images
+ data = open(PathSaveRaw+imageFile, "rb").read()
+
+ hexList = []
+ for ch in data:
+ byt = "%02X" % ch
+ hexList.append(byt)
+
+ for k in range(len(hexList)-1):
+ if hexList[k] == 'FF' and (hexList[k+1] == 'C0' or hexList[k+1] == 'C2'):
+ ow = int(hexList[k+7],16)*256 + int(hexList[k+8],16)
+ oh = int(hexList[k+5],16)*256 + int(hexList[k+6],16)
+
+ elif Segment.startswith('md:'): # mode
+ md = Segment[3:]
+
+ elif Segment.startswith('op:'): # opacity
+ op = float(Segment[3:])*.01
+
+ elif Segment.startswith('o:'): # origin
+ ox, oy = map(int, Segment[2:].split(','))
+
+ elif Segment.startswith('n:'): # name
+ n = Segment[3:-4]
+ OpenBracket = n.find ('[')
+ CloseBracket = n.find (']')
+
+ if OpenBracket != -1 and CloseBracket != -1:
+ RenderLayer = n[OpenBracket+1:CloseBracket]
+ NameShort = n[:OpenBracket]
+
+ else:
+ RenderLayer = n
+ NameShort = n
+
+ os.rename(PathSaveRaw+imageFile, PathSaveRaw+NameShort+'.jpg')
+ if HasAlpha: os.rename(PathSaveRaw+imageFileAlpha, PathSaveRaw+NameShort+'_A'+'.jpg')
+
+ IMGs.append({'LayerMode':md, 'LayerOpacity':op,\
+ 'LayerName':n, 'LayerNameShort':NameShort,\
+ 'RenderLayer':RenderLayer, 'LayerCoords':[ow, oh, ox, oy], 'HasAlpha':HasAlpha})
+
+ else: # Ext == '.xcf':
+ ExtSave = '.png'
+ #-------------------------------------------------
+ # CONFIG
+ XCFInfo = 'xcfinfo'
+ XCF2PNG = 'xcf2png'
+ #-------------------------------------------------
+ # INFO XCF
+
+ CMD = '%s %s%s' % (XCFInfo, Path, File)
+
+ Info = os.popen(CMD)
+
+ IMGs = []
+ for Line in Info.readlines():
+ if Line.startswith ('+'):
+
+ Line = Line.split(' ', 4)
+
+ RenderLayer = Line[4]
+
+ OpenBracket = RenderLayer.find ('[')
+ CloseBracket = RenderLayer.find (']')
+
+ if OpenBracket != -1 and CloseBracket != -1:
+ RenderLayer = RenderLayer[OpenBracket+1:CloseBracket]
+ NameShort = Line[4][:OpenBracket]
+ else:
+ NameShort = Line[4].rstrip()
+ if GroupUntagged:
+ RenderLayer = '__Undefined__'
+ else:
+ RenderLayer = NameShort
+
+ LineThree = Line[3]
+ Slash = LineThree.find('/')
+ if Slash == -1:
+ Mode = LineThree
+ Opacity = 1
+ else:
+ Mode = LineThree[:Slash]
+ Opacity = float(LineThree[Slash+1:LineThree.find('%')])*.01
+
+ IMGs.append ({\
+ 'LayerMode':Mode,\
+ 'LayerOpacity':Opacity,\
+ 'LayerName':Line[4].rstrip(),\
+ 'LayerNameShort':NameShort,\
+ 'LayerCoords':list(map(int, Line[1].replace('x', ' ').replace('+', ' +').replace('-', ' -').split())),\
+ 'RenderLayer':RenderLayer,\
+ 'HasAlpha':True,\
+ })
+ elif Line.startswith('Version'):
+ ResX, ResY = map (int, Line.split()[2].split('x'))
+
+ #-------------------------------------------------
+ # EXTRACT XCF
+ if OpacityMode == 'BAKE':
+ Opacity = ''
+ else:
+ Opacity = ' --percent 100'
+ for Layer in IMGs:
+ CMD = '%s -C %s%s -o %s%s.png "%s"%s' %\
+ (XCF2PNG, Path, File, PathSave, Layer['LayerName'].replace(' ', '_'), Layer['LayerName'], Opacity)
+ os.system(CMD)
+
+ #-------------------------------------------------
+ Scene = bpy.context.scene
+ #-------------------------------------------------
+ # CAMERA
+
+ if SetCamera:
+ bpy.ops.object.camera_add(location=(0, 0, 10))
+
+ Camera = bpy.context.active_object.data
+
+ Camera.type = 'ORTHO'
+ Camera.ortho_scale = ResX * .01
+
+ #-------------------------------------------------
+ # RENDER SETTINGS
+
+ Render = Scene.render
+
+ if SetCamera:
+ Render.resolution_x = ResX
+ Render.resolution_y = ResY
+ Render.resolution_percentage = 100
+ if PremulAlpha: Render.alpha_mode = 'PREMUL'
+
+ #-------------------------------------------------
+ # 3D VIEW SETTINGS
+
+ Scene.game_settings.material_mode = 'GLSL'
+
+ Areas = bpy.context.screen.areas
+
+ for Area in Areas:
+ if Area.type == 'VIEW_3D':
+ Area.active_space.viewport_shade = 'TEXTURED'
+ Area.active_space.show_textured_solid = True
+ Area.active_space.show_floor = False
+
+ #-------------------------------------------------
+ # 3D LAYERS
+
+ def Make3DLayer (Name, NameShort, Z, Coords, RenderLayer, LayerMode, LayerOpacity, HasAlpha):
+
+ # RenderLayer
+
+ if SetupCompo:
+ if not bpy.context.scene.render.layers.get(RenderLayer):
+
+ bpy.ops.scene.render_layer_add()
+
+ LayerActive = bpy.context.scene.render.layers.active
+ LayerActive.name = RenderLayer
+ LayerActive.use_pass_vector = True
+ LayerActive.use_sky = False
+ LayerActive.use_edge_enhance = False
+ LayerActive.use_strand = False
+ LayerActive.use_halo = False
+
+ global LayerNum
+ for i in range (0,20):
+ if not i == LayerNum:
+ LayerActive.layers[i] = False
+
+ bpy.context.scene.layers[LayerNum] = True
+
+ LayerFlags[RenderLayer] = bpy.context.scene.render.layers.active.layers
+
+ LayerList.append([RenderLayer, LayerMode, LayerOpacity])
+
+ LayerNum += 1
+
+ # Object
+ bpy.ops.mesh.primitive_plane_add(\
+ view_align=False,\
+ enter_editmode=False,\
+ rotation=(0, 0, pi))
+
+ bpy.ops.object.rotation_apply()
+
+ Active = bpy.context.active_object
+
+ if SetupCompo:
+ Active.layers = LayerFlags[RenderLayer]
+
+ Active.location = (\
+ (float(Coords[2])-(ResX*0.5))*LayerScale,\
+ (-float(Coords[3])+(ResY*0.5))*LayerScale, Z)
+
+ for Vert in Active.data.vertices:
+ Vert.co[0] += 1
+ Vert.co[1] += -1
+
+ Active.dimensions = float(Coords[0])*LayerScale, float(Coords[1])*LayerScale, 0
+
+ bpy.ops.object.scale_apply()
+
+ bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='MEDIAN')
+
+ Active.show_wire = True
+
+ Active.name = NameShort
+ bpy.ops.mesh.uv_texture_add()
+
+ # Material
+
+ '''if bpy.data.materials.get(NameShort):
+ Mat = bpy.data.materials[NameShort]
+ if not Active.material_slots:
+ bpy.ops.object.material_slot_add()
+ Active.material_slots[0].material = Mat
+ else:'''
+
+ Mat = bpy.data.materials.new(NameShort)
+ Mat.diffuse_color = (1,1,1)
+ Mat.use_raytrace = False
+ Mat.use_shadows = False
+ Mat.use_cast_buffer_shadows = False
+ Mat.use_cast_approximate = False
+ if HasAlpha:
+ Mat.use_transparency = True
+ if OpacityMode == 'MAT': Mat.alpha = LayerOpacity
+ else: Mat.alpha = 0
+ if ShadelessMats: Mat.use_shadeless = True
+
+ if Ext == '.xcf':
+ # Color & Alpha PNG
+ Tex = bpy.data.textures.new(NameShort, 'IMAGE')
+ Tex.extension = 'CLIP'
+ Tex.use_preview_alpha = True
+
+ Img = bpy.data.images.new(NameShort)
+ Img.source = 'FILE'
+ if PremulAlpha: Img.use_premultiply = True
+ Img.filepath = '%s%s%s' % (PathSaveRaw, Name, ExtSave)
+
+ UVFace = Active.data.uv_textures[0].data[0]
+ UVFace.image = Img
+ UVFace.use_image = True
+
+ Tex.image = Img
+
+ Mat.texture_slots.add()
+ TexSlot = Mat.texture_slots[0]
+ TexSlot.texture = Tex
+ TexSlot.use_map_alpha = True
+ TexSlot.texture_coords = 'UV'
+ if OpacityMode == 'TEX': TexSlot.alpha_factor = LayerOpacity
+ elif OpacityMode == 'MAT': TexSlot.blend_type = 'MULTIPLY'
+
+ else: # Ext == '.xjt'
+ # Color JPG
+ Tex = bpy.data.textures.new(NameShort, 'IMAGE')
+ Tex.extension = 'CLIP'
+
+ Img = bpy.data.images.new(NameShort)
+ Img.source = 'FILE'
+ Img.filepath = '%s%s%s' % (PathSaveRaw, Name, ExtSave)
+
+ UVFace = Active.data.uv_textures[0].data[0]
+ UVFace.image = Img
+ UVFace.use_image = True
+
+ Tex.image = Img
+
+ Mat.texture_slots.add()
+ TexSlot = Mat.texture_slots[0]
+ TexSlot.texture = Tex
+ TexSlot.texture_coords = 'UV'
+
+ if HasAlpha:
+ # Alpha JPG
+ Tex = bpy.data.textures.new(NameShort+'_A', 'IMAGE')
+ Tex.extension = 'CLIP'
+ Tex.use_preview_alpha = True
+ Tex.use_alpha = False
+
+ Img = bpy.data.images.new(NameShort+'_A')
+ Img.source = 'FILE'
+ if PremulAlpha: Img.use_premultiply = True
+ Img.filepath = '%s%s_A%s' % (PathSaveRaw, Name, ExtSave)
+
+ Tex.image = Img
+
+ Mat.texture_slots.add()
+ TexSlot = Mat.texture_slots[1]
+ TexSlot.texture = Tex
+ TexSlot.use_map_alpha = True
+ TexSlot.use_map_color_diffuse = False
+ TexSlot.texture_coords = 'UV'
+ if OpacityMode == 'TEX': TexSlot.alpha_factor = LayerOpacity
+ elif OpacityMode == 'MAT': TexSlot.blend_type = 'MULTIPLY'
+
+ if not Active.material_slots:
+ bpy.ops.object.material_slot_add()
+
+ Active.material_slots[0].material = Mat
+
+
+ Z = 0
+ global LayerNum
+ LayerNum = 0
+ LayerFlags = {}
+ LayerList = []
+
+ for Layer in IMGs:
+ Make3DLayer(\
+ Layer['LayerName'].replace(' ', '_'),\
+ Layer['LayerNameShort'].replace(' ', '_'),\
+ Z,\
+ Layer['LayerCoords'],\
+ Layer['RenderLayer'],\
+ Layer['LayerMode'],\
+ Layer['LayerOpacity'],\
+ Layer['HasAlpha'],\
+ )
+
+ Z -= LayerOffset
+
+ if SetupCompo:
+ #-------------------------------------------------
+ # COMPO NODES
+
+ Scene.use_nodes = True
+
+ Tree = Scene.node_tree
+
+ for i in Tree.nodes:
+ Tree.nodes.remove(i)
+
+ LayerList.reverse()
+
+ Offset = 0
+ LayerLen = len(LayerList)
+
+ for Layer in LayerList:
+
+ Offset += 1
+
+ X_Offset = (500*Offset)
+ Y_Offset = (-300*Offset)
+
+ Node = Tree.nodes.new('R_LAYERS')
+ Node.location = (-500+X_Offset, 300+Y_Offset)
+ Node.name = 'R_'+ str(Offset)
+ Node.scene = Scene
+ Node.layer = Layer[0]
+
+ if LayerViewers:
+ Node_V = Tree.nodes.new('VIEWER')
+ Node_V.name = Layer[0]
+ Node_V.location = (-200+X_Offset, 200+Y_Offset)
+
+ Tree.links.new(Node.outputs[0], Node_V.inputs[0])
+
+ if LayerLen > Offset:
+
+ Mode = LayerList[Offset][1] # has to go one step further
+ LayerOpacity = LayerList[Offset][2]
+
+ if not Mode in ('Normal', '-1'):
+
+ Node = Tree.nodes.new('MIX_RGB')
+ if OpacityMode == 'COMPO': Node.inputs['Fac'].default_value[0] = LayerOpacity
+ else: Node.inputs['Fac'].default_value[0] = 1
+ Node.use_alpha = True
+
+ if Mode in ('Addition', '7'): Node.blend_type = 'ADD'
+ elif Mode in ('Subtract', '8'): Node.blend_type = 'SUBTRACT'
+ elif Mode in ('Multiply', '3'): Node.blend_type = 'MULTIPLY'
+ elif Mode in ('DarkenOnly', '9'): Node.blend_type = 'DARKEN'
+ elif Mode in ('Dodge', '16'): Node.blend_type = 'DODGE'
+ elif Mode in ('LightenOnly', '10'): Node.blend_type = 'LIGHTEN'
+ elif Mode in ('Difference', '6'): Node.blend_type = 'DIFFERENCE'
+ elif Mode in ('Divide', '15'): Node.blend_type = 'DIVIDE'
+ elif Mode in ('Overlay', '5'): Node.blend_type = 'OVERLAY'
+ elif Mode in ('Screen', '4'): Node.blend_type = 'SCREEN'
+ elif Mode in ('Burn', '17'): Node.blend_type = 'BURN'
+ elif Mode in ('Color', '13'): Node.blend_type = 'COLOR'
+ elif Mode in ('Value', '14'): Node.blend_type = 'VALUE'
+ elif Mode in ('Saturation', '12'): Node.blend_type = 'SATURATION'
+ elif Mode in ('Hue', '11'): Node.blend_type = 'HUE'
+ elif Mode in ('Softlight', '19'): Node.blend_type = 'SOFT_LIGHT'
+ else: pass
+
+ else:
+ Node = Tree.nodes.new('ALPHAOVER')
+ if OpacityMode == 'COMPO': Node.inputs['Fac'].default_value[0] = LayerOpacity
+ Node.name = 'M_' + str(Offset)
+ Node.location = (300+X_Offset, 250+Y_Offset)
+
+ if MixerViewers:
+ Node_V = Tree.nodes.new('VIEWER')
+ Node_V.name = Layer[0]
+ Node_V.location = (500+X_Offset, 350+Y_Offset)
+
+ Tree.links.new(Node.outputs[0], Node_V.inputs[0])
+
+ else:
+ Node = Tree.nodes.new('COMPOSITE')
+ Node.name = 'Composite'
+ Node.location = (400+X_Offset, 350+Y_Offset)
+
+ Nodes = bpy.context.scene.node_tree.nodes
+
+ if LayerLen > 1:
+ for i in range (1, LayerLen+1):
+ if i == 1:
+ Tree.links.new(Nodes['R_'+str(i)].outputs[0], Nodes['M_'+str(i)].inputs[1])
+ if 1 < i < LayerLen:
+ Tree.links.new(Nodes['M_'+str(i-1)].outputs[0], Nodes['M_'+str(i)].inputs[1])
+ if 1 < i < LayerLen+1:
+ Tree.links.new(Nodes['R_'+str(i)].outputs[0], Nodes['M_'+str(i-1)].inputs[2])
+ if i == LayerLen:
+ Tree.links.new(Nodes['M_'+str(i-1)].outputs[0], Nodes['Composite'].inputs[0])
+ else:
+ Tree.links.new(Nodes['R_1'].outputs[0], Nodes['Composite'].inputs[0])
+
+ for i in Tree.nodes:
+ i.location[0] += -250*Offset
+ i.location[1] += 150*Offset
+
+#------------------------------------------------------------------------
+import os
+import bpy
+from bpy.props import *
+from math import pi
+
+# Operator
+class GIMPImageToScene(bpy.types.Operator):
+ ''''''
+ bl_idname = "import.gimp_image_to_scene"
+ bl_label = "GIMP Image to Scene"
+ bl_description = "Imports GIMP multilayer image files into 3D Scenes"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ filename = StringProperty(name="File Name",
+ description="Name of the file")
+ directory = StringProperty(name="Directory",
+ description="Directory of the file")
+
+ LayerViewers = BoolProperty(name="Layer Viewers",
+ description="Add Viewer nodes to each Render Layer node",
+ default=True)
+
+ MixerViewers = BoolProperty(name="Mixer Viewers",
+ description="Add Viewer nodes to each Mix node",
+ default=True)
+
+ PremulAlpha = BoolProperty(name="Premuliply Alpha",
+ description="Set Image and Render settings to premultiplied alpha",
+ default=True)
+
+ ShadelessMats = BoolProperty(name="Shadeless Material",
+ description="Set Materials as Shadeless",
+ default=True)
+
+ OpacityMode = EnumProperty(name="Opacity Mode",
+ description="Layer Opacity management",
+ items=(
+ ('TEX', 'Texture Alpha Factor', ''),
+ ('MAT', 'Material Alpha Value', ''),
+ ('COMPO', 'Mixer Node Factor', ''),
+ ('BAKE', 'Baked in Image Alpha', '')),
+ default='TEX')
+
+ SetCamera = BoolProperty(name="Set Camera",
+ description="Create an Ortho Camera matching image resolution",
+ default=True)
+
+ SetupCompo = BoolProperty(name="Setup Node Compositing",
+ description="Create a compositing node setup (will delete existing nodes)",
+ default=False)
+
+ GroupUntagged = BoolProperty(name="Group Untagged",
+ description="Layers with no tag go to a single Render Layer",
+ default=False)
+
+ LayerOffset = FloatProperty(name="Layer Separation",
+ description="Distance between each 3D Layer in the Z axis",
+ min=0,
+ default=0.01)
+
+ LayerScale = FloatProperty(name="Layer Scale",
+ description="Scale pixel resolution by Blender units",
+ min=0,
+ default=0.01)
+
+ def draw(self, context):
+ layout = self.layout
+ box = layout.box()
+ box.label('3D Layers:', icon='SORTSIZE')
+ box.prop(self, 'SetCamera', icon='OUTLINER_DATA_CAMERA')
+ box.prop(self, 'OpacityMode', icon='GHOST')
+ if self.OpacityMode == 'COMPO' and self.SetupCompo == False:
+ box.label('Tip: Enable Node Compositing', icon='INFO')
+ box.prop(self, 'PremulAlpha', icon='IMAGE_RGB_ALPHA')
+ box.prop(self, 'ShadelessMats', icon='SOLID')
+ box.prop(self, 'LayerOffset')
+ box.prop(self, 'LayerScale')
+ box = layout.box()
+ box.label('Compositing:', icon='RENDERLAYERS')
+ box.prop(self, 'SetupCompo', icon='NODETREE')
+ if self.SetupCompo:
+ box.prop(self, 'GroupUntagged', icon='IMAGE_ZDEPTH')
+ box.prop(self, 'LayerViewers', icon='NODE')
+ box.prop(self, 'MixerViewers', icon='NODE')
+
+ def execute(self, context):
+ # File Path
+ filename = self.filename
+ directory = self.directory
+
+ # Settings
+ LayerViewers = self.LayerViewers
+ MixerViewers = self.MixerViewers
+ OpacityMode = self.OpacityMode
+ PremulAlpha = self.PremulAlpha
+ ShadelessMats = self.ShadelessMats
+ SetCamera = self.SetCamera
+ SetupCompo = self.SetupCompo
+ GroupUntagged = self.GroupUntagged
+ LayerOffset = self.LayerOffset
+ LayerScale = self.LayerScale
+
+ Ext = None
+ if filename.endswith('.xcf'): Ext = '.xcf'
+ elif filename.endswith('.xjt'): Ext = '.xjt'
+
+ # Call Main Function
+ if Ext:
+ main(filename, directory, LayerViewers, MixerViewers, LayerOffset,\
+ LayerScale, OpacityMode, PremulAlpha, ShadelessMats,\
+ SetCamera, SetupCompo, GroupUntagged, Ext)
+ else:
+ self.report({'ERROR'},"Selected file wasn't valid, try .xcf or .xjt")
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = bpy.context.window_manager
+ wm.fileselect_add(self)
+
+ return {'RUNNING_MODAL'}
+
+
+# Registering / Unregister
+def menu_func(self, context):
+ self.layout.operator(GIMPImageToScene.bl_idname, text="GIMP Image to Scene (.xcf, .xjt)", icon='PLUGIN')
+
+
+def register():
+ bpy.types.INFO_MT_file_import.append(menu_func)
+
+
+def unregister():
+ bpy.types.INFO_MT_file_import.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..5defe909
--- /dev/null
+++ b/io_import_images_as_planes.py
@@ -0,0 +1,347 @@
+# ##### 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 Images as Planes",
+ "author": "Florian Meyer (tstscr)",
+ "version": (1, 0),
+ "blender": (2, 5, 5),
+ "api": 33754,
+ "location": "File > Import > Images as Planes",
+ "description": "Imports images and creates planes with the appropriate aspect ratio. "\
+ "The images are mapped to the planes.",
+ "warning": "",
+ "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, os, mathutils
+from bpy.props import *
+from add_utils import *
+from io_utils import ImportHelper, load_image
+
+## GLOBAL VARS ##
+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']}
+EXT_VALS = [val for val in EXT_LIST.values()]
+EXTENSIONS = []
+for i in EXT_VALS:
+ EXTENSIONS.extend(i)
+
+## FUNCTIONS ##
+def set_image_options(self, image):
+ image.use_premultiply = self.use_premultiply
+
+def create_image_textures(self, image):
+ #look for texture with importsettings
+ for texture in bpy.data.textures:
+ if texture.type == 'IMAGE'\
+ and texture.image\
+ and texture.image.filepath == image.filepath:
+ if self.use_transparency:
+ texture.use_alpha = True
+ else:
+ texture.use_alpha = False
+ return texture
+
+ #if no texture is found: create one
+ texture = bpy.data.textures.new(name=os.path.split(image.filepath)[1],
+ type='IMAGE')
+ texture.image = image
+ if self.use_transparency:
+ texture.use_alpha = True
+ else:
+ texture.use_alpha = False
+ return texture
+
+def create_material_for_texture(self, texture):
+ #look for material with the needed texture
+ for material in bpy.data.materials:
+ if material.texture_slots[0]\
+ and material.texture_slots[0].texture == texture:
+ if self.use_transparency:
+ material.alpha = 0
+ material.specular_alpha = 0
+ material.texture_slots[0].use_map_alpha = True
+ else:
+ material.alpha = 1
+ material.specular_alpha = 1
+ material.texture_slots[0].use_map_alpha = False
+ material.use_transparency = self.use_transparency
+ material.transparency_method = self.transparency_method
+ material.use_shadeless = self.use_shadeless
+ return material
+
+ # if no material found: create one
+ material = bpy.data.materials.new(name=os.path.split(texture.image.filepath)[1])
+ slot = material.texture_slots.add()
+ slot.texture = texture
+ slot.texture_coords = 'UV'
+ if self.use_transparency:
+ slot.use_map_alpha = True
+ material.alpha = 0
+ material.specular_alpha = 0
+ else:
+ material.alpha = 1
+ material.specular_alpha = 1
+ slot.use_map_alpha = False
+ material.use_transparency = self.use_transparency
+ material.transparency_method = self.transparency_method
+ material.use_shadeless = self.use_shadeless
+
+ return material
+
+def create_image_plane(self, context, material):
+ img = material.texture_slots[0].texture.image
+ x = img.size[0] / img.size[1]
+ y = 1
+
+ if self.use_dimension:
+ x = (img.size[0] * (1.0 / self.factor)) * 0.5
+ y = (img.size[1] * (1.0 / self.factor)) * 0.5
+
+ verts = [(-x, -y, 0),
+ (x, -y, 0),
+ (x, y, 0),
+ (-x, y, 0)]
+ faces = [[0, 1, 2, 3]]
+
+ mesh_data = bpy.data.meshes.new(img.name)
+ mesh_data.from_pydata(verts, [], faces)
+ mesh_data.update()
+ add_object_data(context, mesh_data, operator=self)
+ plane = context.scene.objects.active
+ plane.data.uv_textures.new()
+ plane.data.materials.append(material)
+ plane.data.uv_textures[0].data[0].image = img
+ plane.data.uv_textures[0].data[0].use_image = True
+ plane.data.uv_textures[0].data[0].blend_type = 'ALPHA'
+ plane.data.uv_textures[0].data[0].use_twoside = True
+ return plane
+
+def generate_paths(self):
+ directory, file = os.path.split(self.filepath)
+
+ if file and not self.all_in_directory:
+ #test for extension
+ if not os.path.splitext(file)[1].lstrip('.').lower() in EXTENSIONS:
+ return [], directory
+
+ return [self.filepath], directory
+
+ if not file or self.all_in_directory:
+ imagepaths = []
+ files_in_directory = os.listdir(directory)
+ #clean files from nonimages
+ files_in_directory = [file for file in files_in_directory
+ if os.path.splitext(file)[1].lstrip('.').lower()
+ in EXTENSIONS]
+ #clean from unwanted extensions
+ if self.extension != '*':
+ files_in_directory = [file for file in files_in_directory
+ if os.path.splitext(file)[1].lstrip('.').lower()
+ in EXT_LIST[self.extension]]
+ #create paths
+ for file in files_in_directory:
+ imagepaths.append(os.path.join(directory, file))
+
+ #print(imagepaths)
+ return imagepaths, directory
+
+def align_planes(self, planes):
+ gap = self.align_offset
+ offset = 0
+ for i, plane in enumerate(planes):
+ offset += (plane.dimensions.x / 2) + gap
+ if i == 0: continue
+ move_local = mathutils.Vector((offset, 0, 0))
+ move_world = plane.location + move_local * plane.matrix_world.copy().invert()
+ plane.location += move_world
+ offset += (plane.dimensions.x / 2)
+
+##### MAIN #####
+def import_images(self, context):
+ import_list, directory = generate_paths(self)
+ images = []
+ textures = []
+ materials = []
+ planes = []
+
+ for path in import_list:
+ images.append(load_image(path, directory))
+
+ for image in images:
+ set_image_options(self, image)
+ textures.append(create_image_textures(self, image))
+
+ for texture in textures:
+ materials.append(create_material_for_texture(self, texture))
+
+ for material in materials:
+ plane = create_image_plane(self, context, material)
+ planes.append(plane)
+
+ context.scene.update()
+ if self.align:
+ align_planes(self, planes)
+
+ for plane in planes:
+ plane.select = True
+
+ self.report(type='INFO',
+ message='Added %i Image Plane(s)' %len(planes))
+
+##### OPERATOR #####
+class IMPORT_OT_image_to_plane(bpy.types.Operator, ImportHelper, AddObjectHelper):
+ ''''''
+ bl_idname = "import.image_to_plane"
+ bl_label = "Import Images as Planes"
+ bl_description = "Create mesh plane(s) from image files" \
+ " with the appropiate aspect ratio."
+ bl_options = {'REGISTER', 'UNDO'}
+
+ ## OPTIONS ##
+ all_in_directory = BoolProperty(name="All in directory",
+ description="Import all image files (of the selected type)" \
+ " in this directory.",
+ default=False)
+ align = BoolProperty(name='Align Planes',
+ description='Create Planes in a row',
+ default=True)
+ align_offset = FloatProperty(name='Offset',
+ description='Space between Planes',
+ min=0, soft_min=0,
+ default=0.1)
+ 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)
+ use_dimension = 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)
+
+ ## MATERIAL OPTIONS ##
+ use_shadeless = BoolProperty(name="Shadeless",
+ description="Set material to shadeless",
+ default=False)
+ use_transparency = BoolProperty(name="Use alpha",
+ description="Use alphachannel for transparency.",
+ default=False)
+ tEnum = [
+ ('Z_TRANSPARENCY',
+ 'Z Transparency',
+ 'Use alpha buffer for transparent faces'),
+ ('RAYTRACE',
+ 'Raytrace',
+ 'Use raytracing for transparent refraction rendering.')]
+ transparency_method = EnumProperty(name="Transp. Method",
+ description="Transparency Method",
+ items=tEnum)
+
+ ## IMAGE OPTIONS ##
+ use_premultiply = BoolProperty(name="Premultiply",
+ description="Premultiply image",
+ default=False)
+
+ ## DRAW ##
+ def draw(self, context):
+ layout = self.layout
+ box = layout.box()
+ box.label('Import Options:', icon='FILTER')
+ box.prop(self, 'all_in_directory')
+ box.prop(self, 'extension', icon='FILE_IMAGE')
+ box.prop(self, 'align')
+ box.prop(self, 'align_offset')
+ box = layout.box()
+ box.label('Material mappings:', icon='MATERIAL')
+ box.prop(self, 'use_shadeless')
+ box.prop(self, 'use_transparency')
+ box.prop(self, 'use_premultiply')
+ box.prop(self, 'transparency_method', expand=True)
+ box = layout.box()
+ box.label('Plane dimensions:', icon='ARROW_LEFTRIGHT')
+ box.prop(self, 'use_dimension')
+ box.prop(self, 'factor', expand=True)
+
+
+ ## EXECUTE ##
+ def execute(self, context):
+ #the add utils don't work in this case
+ #because many objects are added
+ #disable relevant things beforehand
+ editmode = context.user_preferences.edit.use_enter_edit_mode
+ context.user_preferences.edit.use_enter_edit_mode = False
+ if context.active_object\
+ and context.active_object.mode == 'EDIT':
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ import_images(self, context)
+
+ context.user_preferences.edit.use_enter_edit_mode = editmode
+ return {'FINISHED'}
+
+
+
+
+##### REGISTER #####
+
+def import_images_button(self, context):
+ self.layout.operator(IMPORT_OT_image_to_plane.bl_idname, text="Images as Planes", icon='PLUGIN')
+
+def register():
+ bpy.types.INFO_MT_file_import.append(import_images_button)
+def unregister():
+ bpy.types.INFO_MT_file_import.remove(import_images_button)
+if __name__ == '__main__':
+ register()
diff --git a/io_import_scene_dxf.py b/io_import_scene_dxf.py
new file mode 100644
index 00000000..4f1924b4
--- /dev/null
+++ b/io_import_scene_dxf.py
@@ -0,0 +1,2506 @@
+# ##### 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 #####
+
+"""
+Release note by migius (DXF support maintainer) 2011.01.02:
+Script supports only a small part of DXF specification:
+- imports LINE, ARC, CIRCLE, ELLIPSE, SOLID, TRACE, POLYLINE, LWPOLYLINE
+- imports TEXT, MTEXT
+- supports 3d-rotation of entities (210 group)
+- supports THICKNESS for SOLID, TRACE, LINE, ARC, CIRCLE, ELLIPSE
+- ignores WIDTH, THICKNESS, BULGE in POLYLINE/LWPOLYLINE
+- ignores face-data in POLYFACE / POLYMESH
+- ignores TEXT 2d-rotation
+- ignores hierarchies (BLOCK, INSERT, GROUP)
+- ignores LAYER
+- ignores COLOR, LINEWIDTH, LINESTYLE
+
+This script is a temporary solution.
+Probably no more improvements will be done to this script.
+The full-feature importer script from 2.49 will be back in 2.6 release.
+
+Installation:
+Place this file to Blender addons directory (on Windows it is %Blender_directory%\2.53\scripts\addons\)
+You must activate the script in the "Add-Ons" tab (user preferences).
+Access it from File > Import menu.
+
+History:
+ver 0.1.3 - 2011.01.02 by migius
+- added draw curves as sequence for "Draw_as_Curve"
+- added toggle "Draw as one" as user preset in UI
+- added draw POINT as mesh-vertex
+- added draw_THICKNESS for LINE, ARC, CIRCLE, ELLIPSE, LWPOLYLINE and POLYLINE
+- added draw_THICKNESS for SOLID, TRACE
+ver 0.1.2 - 2010.12.27 by migius
+- added draw() for TRACE
+- fixed wrong vertex order in SOLID
+- added CIRCLE resolution as user preset in UI
+- added closing segment for circular LWPOLYLINE and POLYLINE
+- fixed registering for 2.55beta
+ver 0.1.1 - 2010.09.07 by migius
+- fixed dxf-file names recognition limited to ".dxf"
+- fixed registering for 2.53beta
+ver 0.1 - 2010.06.10 by Thomas Larsson
+"""
+
+bl_addon_info = {
+ 'name': 'Import Autocad DXF (.dxf)',
+ 'author': 'Thomas Larsson',
+ 'version': (0,1,3),
+ 'blender': (2, 5, 6),
+ 'api': 32738,
+ 'location': 'File > Import',
+ 'description': 'Import files in the Autocad DXF format (.dxf)',
+ 'warning': 'supporting only a sub-set of DXF specification',
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/Scripts/Import-Export/DXF_Importer',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?func=detail&aid=23480&group_id=153&atid=469',
+ 'category': 'Import-Export'}
+
+__version__ = '.'.join([str(s) for s in bl_addon_info['version']])
+
+import os
+import codecs
+import math
+from math import sin, cos, radians
+import bpy
+import mathutils
+from mathutils import Vector, Matrix
+
+#
+# Global flags
+#
+
+T_Merge = 0x01
+T_NewScene = 0x02
+T_Curves = 0x04
+T_DrawOne = 0x08
+T_Debug = 0x10
+T_Verbose = 0x20
+T_ThicON = 0x40
+
+toggle = T_Merge | T_NewScene | T_DrawOne | T_ThicON
+theCircleRes = 32
+theMergeLimit = 1e-5
+
+#
+# class CSection:
+#
+
+class CSection:
+ type = None
+
+ def __init__(self):
+ self.data = []
+
+ def display(self):
+ print("Section", self.type)
+ for datum in self.data:
+ datum.display()
+
+#
+# class CTable:
+#
+
+class CTable:
+ def __init__(self):
+ self.type = None
+ self.name = None
+ self.handle = None
+ self.owner = None
+ self.subclass = None
+ self.nEntries = 0
+ def display(self):
+ print("Table %s %s %s %s %s %d" % (self.type, self.name, self.handle, self.owner, self.subclass, self.nEntries))
+
+#
+# class CEntity:
+#
+class CEntity:
+ def __init__(self, typ, drawtype):
+ self.type = typ
+ self.drawtype = drawtype
+ self.handle = None
+ self.owner = None
+ self.subclass = None
+ self.layer = 0
+ self.color = 0
+ self.invisible = 0
+ self.linetype_name = ''
+ self.linetype_scale = 1.0
+ self.paperspace = 0
+ #self.normal = Vector((0,0,1))
+
+ def display(self):
+ print("Entity %s %s %s %s %s %s %x" %
+ (self.type, self.handle, self.owner, self.subclass, self.layer, self.color, self.invisible))
+
+ def build(self, vn=0):
+ global toggle
+ if toggle & T_Debug:
+ raise NameError("Warning: can not build - unsupported entity type: %s" % self.type)
+ return(([], [], [], vn))
+
+ def draw(self):
+ global toggle
+ if toggle & T_Debug:
+ raise NameError("Warning: can not draw - unsupported entity type: %s" % self.type)
+ return
+
+
+DxfCommonAttributes = {
+ 5 : 'handle',
+ 6 : 'linetype_name',
+ 8 : 'layer',
+ 48 : 'linetype_scale',
+ 60 : 'invisible',
+ 62 : 'color',
+ 67 : 'paperspace',
+ 100 : 'subclass',
+ 330 : 'owner',
+ 360 : 'owner',
+}
+
+#
+# class C3dFace(CEntity):
+# 10 : 'point0.x', 20 : 'point0.y', 30 : 'point0.z',
+# 11 : 'point1.x', 21 : 'point1.y', 31 : 'point1.z',
+# 12 : 'point2.x', 22 : 'point2.y', 32 : 'point2.z',
+# 13 : 'point3.x', 23 : 'point3.y', 33 : 'point3.z',
+# 70 : 'flags',
+#
+
+class C3dFace(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, '3DFACE', 'Mesh')
+ self.point0 = Vector()
+ self.point1 = Vector()
+ self.point2 = Vector()
+ self.point3 = Vector()
+
+ def display(self):
+ CEntity.display(self)
+ print(self.point0)
+ print(self.point1)
+ print(self.point2)
+ print(self.point3)
+
+ def build(self, vn=0):
+ verts = [self.point0, self.point1, self.point2]
+ if self.point3 == Vector((0,0,0)) or self.point2 == self.point3:
+ faces = [(vn+0, vn+1, vn+2)]
+ vn += 3
+ else:
+ verts.append( self.point3 )
+ faces = [(vn+0, vn+1, vn+2, vn+3)]
+ vn += 4
+ return((verts, [], faces, vn))
+
+#
+# class C3dSolid(CEntity):
+# 1 : 'data', 3 : 'more', 70 : 'version',
+#
+
+class C3dSolid(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, '3DSOLID', 'Mesh')
+ self.data = None
+ self.more = None
+ self.version = 0
+
+#
+# class CAcadProxyEntity(CEntity):
+# 70 : 'format',
+# 90 : 'id', 91 : 'class', 92 : 'graphics_size', 93 : 'entity_size', 95: 'format',
+# 310 : 'data', 330 : 'id1', 340 : 'id2', 350 : 'id3', 360 : 'id4',
+#
+
+class CAcadProxyEntity(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'ACAD_PROXY_ENTITY', None)
+
+
+#
+# class CArc(CEntity):
+# 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+# 40 : 'radius',
+# 50 : 'start_angle', 51 : 'end_angle'
+#
+
+class CArc(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'ARC', 'Mesh')
+ self.center = Vector()
+ self.radius = 0.0
+ self.start_angle = 0.0
+ self.end_angle = 0.0
+ self.thickness = 0.0
+ self.normal = Vector((0,0,1))
+
+ def display(self):
+ CEntity.display(self)
+ print(self.center)
+ print("%.4f %.4f %.4f " % (self.radius, self.start_angle, self.end_angle))
+
+ def build(self, vn=0):
+ start, end = self.start_angle, self.end_angle
+ if end > 360: end = end % 360.0
+ if end < start: end +=360.0
+ angle = end - start
+
+ deg2rad = math.pi/180.0
+ start *= deg2rad
+ end *= deg2rad
+ dphi = end - start
+ phi0 = start
+ w = dphi/theCircleRes
+ r = self.radius
+ center = self.center
+ v0 = vn
+ points = []
+ edges, faces = [], []
+ for n in range(theCircleRes):
+ s = math.sin(n*w + phi0)
+ c = math.cos(n*w + phi0)
+ v = center + Vector((r*c, r*s, 0.0))
+ points.append(v)
+ pn = len(points)
+ thic = self.thickness
+ t_vector = Vector((0, 0, thic))
+ if thic != 0 and (toggle & T_ThicON):
+ thic_points = [v + t_vector for v in points]
+ if thic < 0.0:
+ thic_points.extend(points)
+ points = thic_points
+ else:
+ points.extend(thic_points)
+ faces = [(v0+nr+0,v0+nr+1,v0+pn+nr+1,v0+pn+nr+0) for nr in range(pn)]
+ faces.pop()
+ self.drawtype = 'Mesh'
+ vn += 2*pn
+ else:
+ edges = [(v0+nr+0,v0+nr+1) for nr in range(pn)]
+ edges.pop()
+ vn += pn
+
+ if self.normal!=Vector((0,0,1)):
+ ma = getOCS(self.normal)
+ if ma:
+ #ma.invert()
+ points = [v * ma for v in points]
+ #print ('arc vn=', vn)
+ #print ('faces=', len(faces))
+ return ((points, edges, faces, vn))
+
+#
+# class CArcAlignedText(CEntity):
+# 1 : 'text', 2 : 'font', 3 : 'bigfont', 7 : 'style',
+# 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+# 40 : 'radius', 41 : 'width', 42 : 'height', 43 : 'spacing',
+# 44 : 'offset', 45 : 'right_offset', 46 : 'left_offset',
+# 50 : 'start_angle', 51 : 'end_angle',
+# 70 : 'order', 71 : 'direction', 72 : 'alignment', 73 : 'side',
+# 74 : 'bold', 75 : 'italic', 76 : 'underline',
+# 77 : 'character_set', 78 : 'pitch', 79 'fonttype',
+# 90 : 'color',
+# 280 : 'wizard', 330 : 'id'
+#
+
+class CArcAlignedText(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'ARCALIGNEDTEXT', 'Mesh')
+ self.text = ""
+ self.style = ""
+ self.center = Vector()
+ self.radius = 0.0
+ self.width = 1.0
+ self.height = 1.0
+ self.spacing = 1.0
+ self.offset = 0.0
+ self.right_offset = 0.0
+ self.left_offset = 0.0
+ self.start_angle = 0.0
+ self.end_angle = 0.0
+ self.order = 0
+ self.directions = 0
+ self.alignment = 0
+ self.side = 0
+ self.bold = 0
+ self.italic = 0
+ self.underline = 0
+ self.character_set = 0
+ self.pitch = 0
+ self.fonttype = 0
+ self.color = 0
+ self.wizard = None
+ self.id = None
+ self.normal = Vector((0,0,1))
+
+
+#
+# class CAttdef(CEntity):
+# 1 : 'text', 2 : 'tag', 3 : 'prompt', 7 : 'style',
+# 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+# 11 : 'alignment_point.x', 21 : 'alignment_point.y', 31 : 'alignment_point.z',
+# 40 : 'height', 41 : 'x_scale',
+# 50 : 'rotation_angle', 51 : 'oblique_angle',
+# 70 : 'flags', 71 : 'text_generation_flags',
+# 72 : 'horizontal_justification', 74 : 'vertical_justification',
+#
+
+class CAttdef(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'ATTDEF', None)
+ self.value = ""
+ self.tag = ""
+ self.prompt = ""
+ self.style = ""
+ self.insertion_point = Vector()
+ self.alignment_point = Vector()
+ self.height = 1.0
+ self.x_scale = 1.0
+ self.rotation_angle = 0.0
+ self.oblique_angle = 0.0
+ self.flags = 0
+ self.text_generation_flags = 0
+ self.horizontal_justification = 0.0
+ self.vertical_justification = 0.0
+ self.normal = Vector((0,0,1))
+
+ def draw(self):
+ drawText(self.text, self.insertion_point, self.height, self.x_scale, self.rotation_angle, self.oblique_angle, self.normal)
+ return
+
+#
+# class CAttrib(CEntity):
+# 1 : 'text', 2 : 'tag', 3 : 'prompt', 7 : 'style',
+# 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+# 11 : 'alignment_point.x', 21 : 'alignment_point.y', 31 : 'alignment_point.z',
+# 40 : 'height', 41 : 'x_scale',
+# 50 : 'rotation_angle', 51 : 'oblique_angle',
+# 70 : 'flags', 73 : 'length',
+# 71 : 'text_generation_flags', 72 : 'horizontal_justification', 74 : 'vertical_justification',
+#
+
+class CAttrib(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'ATTRIB', None)
+ self.text = ""
+ self.tag = ""
+ self.prompt = ""
+
+ self.style = ""
+ self.insertion_point = Vector()
+ self.alignment_point = Vector()
+ self.height = 1.0
+ self.x_scale = 1.0
+ self.rotation_angle = 0.0
+ self.oblique_angle = 0.0
+ self.flags = 0
+ self.length = 1.0
+ self.text_generation_flags = 0
+ self.horizontal_justification = 0.0
+ self.vertical_justification = 0.0
+ self.normal = Vector((0,0,1))
+
+ def draw(self):
+ drawText(self.text, self.insertion_point, self.height, self.x_scale, self.rotation_angle, self.oblique_angle, self.normal)
+ return
+
+
+#
+# class CBlock(CEntity):
+# 1 : 'xref', 2 : 'name', 3 : 'also_name',
+# 10 : 'base_point.x', 20 : 'base_point.y', 30 : 'base_point.z',
+# 40 : 'size', 41 : 'x_scale',
+# 50 : 'rotation_angle', 51 : 'oblique_angle',
+# 70 : 'flags',
+#
+
+class CBlock(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'BLOCK', None)
+ self.xref = ""
+ self.name = ""
+ self.also_name = ""
+ self.base_point = Vector()
+ self.size = 1.0
+ self.x_scale = 1.0
+ self.rotation_angle = 0.0
+ self.oblique_angle = 0.0
+ self.flags = 0
+ self.normal = Vector((0,0,1))
+
+ def display(self):
+ CEntity.display(self)
+ print("%s %s %s " % (self.xref, self.name, self.also_name))
+ print(self.base_point)
+
+ def draw(self):
+ # Todo
+ return
+
+#
+# class CCircle(CEntity):
+# 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+# 40 : 'radius'
+#
+
+class CCircle(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'CIRCLE', 'Mesh')
+ self.center = Vector()
+ self.radius = 0.0
+ self.thickness = 0.0
+ self.normal = Vector((0,0,1))
+
+ def display(self):
+ CEntity.display(self)
+ print(self.center)
+ print("%.4f" % self.radius)
+
+ def build(self, vn=0):
+ w = 2*math.pi/theCircleRes
+ r = self.radius
+ center = self.center
+ points = []
+ edges, faces = [], []
+ v0 = vn
+ for n in range(theCircleRes):
+ s = math.sin(n*w)
+ c = math.cos(n*w)
+ v = center + Vector((r*c, r*s, 0))
+ points.append(v)
+
+ pn = len(points)
+ thic = self.thickness
+ t_vector = Vector((0, 0, thic))
+ if thic != 0 and (toggle & T_ThicON):
+ thic_points = [v + t_vector for v in points]
+ if thic < 0.0:
+ thic_points.extend(points)
+ points = thic_points
+ else:
+ points.extend(thic_points)
+ faces = [(v0+nr,v0+nr+1,pn+v0+nr+1,pn+v0+nr) for nr in range(pn)]
+ nr = pn -1
+ faces[-1] = (v0+nr,v0,pn+v0,pn+v0+nr)
+ self.drawtype = 'Mesh'
+ vn += 2*pn
+ else:
+ edges = [(v0+nr,v0+nr+1) for nr in range(pn)]
+ nr = pn -1
+ edges[-1] = (v0+nr,v0)
+ vn += pn
+ if self.normal!=Vector((0,0,1)):
+ ma = getOCS(self.normal)
+ if ma:
+ #ma.invert()
+ points = [v * ma for v in points]
+ #print ('cir vn=', vn)
+ #print ('faces=',len(faces))
+ return( (points, edges, faces, vn) )
+
+#
+# class CDimension(CEntity):
+# 1 : 'text', 2 : 'name', 3 : 'style',
+# 10 : 'def_point.x', 20 : 'def_point.y', 30 : 'def_point.z',
+# 11 : 'mid_point.x', 21 : 'mid_point.y', 31 : 'mid_point.z',
+# 12 : 'vector.x', 22 : 'vector.y', 32 : 'vector.z',
+# 13 : 'def_point2.x', 23 : 'def_point2.y', 33 : 'def_point2.z',
+# 14 : 'vector2.x', 24 : 'vector2.y', 34 : 'vector2.z',
+# 15 : 'vector3.x', 25 : 'vector3.y', 35 : 'vector3.z',
+# 16 : 'vector4.x', 26 : 'vector4.y', 36 : 'vector4.z',
+# 70 : 'dimtype',
+#
+
+class CDimension(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'DIMENSION', None)
+ self.text = ""
+ self.name = ""
+ self.style = ""
+ self.def_point = Vector()
+ self.mid_point = Vector()
+ self.vector = Vector()
+ self.def_point2 = Vector()
+ self.vector2 = Vector()
+ self.vector3 = Vector()
+ self.vector4 = Vector()
+ self.dimtype = 0
+ self.normal = Vector((0,0,1))
+
+ def draw(self):
+ return
+
+#
+# class CEllipse(CEntity):
+# 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+# 11 : 'end_point.x', 21 : 'end_point.y', 31 : 'end_point.z',
+# 40 : 'ratio', 41 : 'start', 42 : 'end',
+#
+
+class CEllipse(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'ELLIPSE', 'Mesh')
+ self.center = Vector()
+ self.end_point = Vector()
+ self.ratio = 1.0
+ self.start = 0.0
+ self.end = 2*math.pi
+ self.thickness = 0.0
+ self.normal = Vector((0,0,1))
+
+ def display(self):
+ CEntity.display(self)
+ print(self.center)
+ print("%.4f" % self.ratio)
+
+ def build(self, vn=0):
+ dphi = (self.end - self.start)
+ phi0 = self.start
+ w = dphi/theCircleRes
+ r = self.end_point.length
+ f = self.ratio
+ a = self.end_point.x/r
+ b = self.end_point.y/r
+ center = self.center
+ v0 = vn
+ points = []
+ edges, faces = [], []
+ for n in range(theCircleRes):
+ x = r*math.sin(n*w + phi0)
+ y = f*r*math.cos(n*w + phi0)
+ v = (center.x - a*x + b*y, center.y - a*y - b*x, center.z)
+ points.append(v)
+
+ pn = len(points)
+ thic = self.thickness
+ t_vector = Vector((0, 0, thic))
+ if thic != 0 and (toggle & T_ThicON):
+ thic_points = [v + t_vector for v in points]
+ if thic < 0.0:
+ thic_points.extend(points)
+ points = thic_points
+ else:
+ points.extend(thic_points)
+ faces = [(v0+nr,v0+nr+1,pn+v0+nr+1,pn+v0+nr) for nr in range(pn)]
+ nr = pn -1
+ faces[-1] = (v0+nr,v0,pn+v0,pn+v0+nr)
+ #self.drawtype = 'Mesh'
+ vn += 2*pn
+ else:
+ edges = [(v0+nr,v0+nr+1) for nr in range(pn)]
+ nr = pn -1
+ edges[-1] = (v0+nr,v0)
+ vn += pn
+
+
+ if thic != 0 and (toggle & T_ThicON):
+ pass
+ if self.normal!=Vector((0,0,1)):
+ ma = getOCS(self.normal)
+ if ma:
+ #ma.invert()
+ points = [v * ma for v in points]
+ return ((points, edges, faces, vn))
+
+#
+# class CHatch(CEntity):
+# 2 : 'pattern',
+# 10 : 'point.x', 20 : 'point.y', 30 : 'point.z',
+# 41 : 'scale', 47 : 'pixelsize', 52 : 'angle',
+# 70 : 'fill', 71 : 'associativity', 75: 'style', 77 : 'double',
+# 78 : 'numlines', 91 : 'numpaths', 98 : 'numseeds',
+#
+
+class CHatch(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'HATCH', None)
+ self.pattern = 0
+ self.point = Vector()
+ self.scale = 1.0
+ self.pixelsize = 1.0
+ self.angle = 0.0
+ self.fill = 0
+ self.associativity = 0
+ self.style = 0
+ self.double = 0
+ self.numlines = 0
+ self.numpaths = 0
+ self.numseeds = 0
+ self.normal = Vector((0,0,1))
+
+
+# class CImage(CEntity):
+# 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+# 11 : 'u_vector.x', 21 : 'u_vector.y', 31 : 'u_vector.z',
+# 12 : 'v_vector.x', 22 : 'v_vector.y', 32 : 'v_vector.z',
+# 13 : 'size.x', 23 : 'size.y', 33 : 'size.z',
+# 14 : 'clip.x', 24 : 'clip.y', 34 : 'clip.z',
+# 70 : 'display', 71 : 'cliptype',
+# 90 : 'version',
+# 280 : 'clipstate', 281 : 'brightness', 282 : 'contrast', 283 : 'fade',
+# 340 : 'image', 360 : 'reactor'
+#
+
+class CImage(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'IMAGE', None)
+ self.insertion_point = Vector()
+ self.u_vector = Vector()
+ self.v_vector = Vector()
+ self.size = Vector()
+ self.clip = Vector()
+ self.display = 0
+ self.cliptype = 0
+ self.version = 1
+ self.clipstate = 0
+ self.brightness = 0
+ self.constrast = 0
+ self.fade = 0
+ self.image = None
+ self.reactor = None
+ self.normal = Vector((0,0,1))
+
+#
+# class CInsert(CEntity):
+# 1 : 'attributes_follow', 2 : 'name',
+# 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+# 41 : 'x_scale', 42 : 'y_scale', 43 : 'z_scale',
+# 44 : 'column_spacing', 45 : 'row_spacing',
+# 50 : 'rotation_angle', 66 : 'attributes_follow',
+# 70 : 'column_count', 71 : 'row_count',
+#
+
+class CInsert(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'INSERT', None)
+ self.attributes_follow = 1
+ self.name = ""
+ self.insertion_point = Vector()
+ self.x_scale = 1.0
+ self.y_scale = 1.0
+ self.z_scale = 1.0
+ self.column_spacing = 1.0
+ self.row_spacing = 1.0
+ self.rotation_angle = 0.0
+ self.column_count = 1
+ self.row_count = 1
+ self.attributes_follow = 0
+ self.normal = Vector((0,0,1))
+
+ def display(self):
+ CEntity.display(self)
+ print(self.insertion_point)
+
+ def draw(self):
+ # Todo
+ return
+
+#
+# class CLeader(CEntity):
+# 3 : 'style',
+# 10 : ['new_vertex(data)'], 20 : 'vertex.y', 30 : 'vertex.z',
+# 40 : 'height', 41 : 'width',
+# 71 : 'arrowhead', 72 : 'pathtype', 73 : 'creation',
+# 74 : 'hookdir', 75 : 'hookline', 76 : 'numverts', 77 : 'color',
+# 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+# 211 : 'horizon.x', 221 : 'horizon.y', 231 : 'horizon.z',
+# 212 : 'offset_ins.x', 222 : 'offset_ins.y', 232 : 'offset_ins.z',
+# 213 : 'offset_ann.x', 223 : 'offset_ann.y', 233 : 'offset_ann.z',
+#
+
+class CLeader(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'LEADER', 'Mesh')
+ self.style = ""
+ self.vertex = None
+ self.verts = []
+ self.height = 1.0
+ self.width = 1.0
+ self.arrowhead = 0
+ self.pathtype = 0
+ self.creation = 0
+ self.hookdir = 0
+ self.hookline = 0
+ self.numverts = 0
+ self.color = 0
+ self.normal = Vector((0,0,1))
+ self.horizon = Vector()
+ self.offset_ins = Vector()
+ self.offset_ann = Vector()
+
+ def new_vertex(self, data):
+ self.vertex = Vector()
+ self.vertex.x = data
+ self.verts.append(self.vertex)
+
+ def build(self, vn=0):
+ edges = []
+ for v in self.verts:
+ edges.append((vn, vn+1))
+ vn += 1
+ edges.pop()
+ return (self.verts, edges, [], vn)
+
+# class CLwPolyLine(CEntity):
+# 10 : ['new_vertex(data)'], 20 : 'vertex.y', 30 : 'vertex.z',
+# 38 : 'elevation', 39 : 'thickness',
+# 40 : 'start_width', 41 : 'end_width', 42 : 'bulge', 43 : 'constant_width',
+# 70 : 'flags', 90 : 'numverts'
+#
+
+class CLWPolyLine(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'LWPOLYLINE', None)
+ self.vertex = None
+ self.verts = []
+ self.elevation = 0
+ self.thickness = 0.0
+ self.start_width = 0.0
+ self.end_width = 0.0
+ self.bulge = 0.0
+ self.constant_width = 0.0
+ self.flags = 0
+ self.numverts = 0
+ self.normal = Vector((0,0,1))
+
+ def new_vertex(self, data):
+ self.vertex = Vector()
+ self.vertex.x = data
+ self.verts.append(self.vertex)
+
+ def build(self, vn=0):
+ edges = []
+ v_start = vn
+ for v in self.verts:
+ edges.append((vn, vn+1))
+ vn += 1
+ if self.flags & PL_CLOSED:
+ edges[-1] = (vn-1, v_start)
+ else:
+ edges.pop()
+ verts = self.verts
+ if self.normal!=Vector((0,0,1)):
+ ma = getOCS(self.normal)
+ if ma:
+ #ma.invert()
+ verts = [v * ma for v in verts]
+ return (verts, edges, [], vn-1)
+
+#
+# class CLine(CEntity):
+# 10 : 'start_point.x', 20 : 'start_point.y', 30 : 'start_point.z',
+# 11 : 'end_point.x', 21 : 'end_point.y', 31 : 'end_point.z',
+# 39 : 'thickness',
+#
+
+class CLine(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'LINE', 'Mesh')
+ self.start_point = Vector()
+ self.end_point = Vector()
+ self.thickness = 0.0
+ self.normal = Vector((0,0,1))
+
+ def display(self):
+ CEntity.display(self)
+ print(self.start_point)
+ print(self.end_point)
+
+ def build(self, vn=0):
+ points = [self.start_point, self.end_point]
+ faces, edges = [], []
+ n = vn
+ thic = self.thickness
+ if thic != 0 and (toggle & T_ThicON):
+ t_vector = thic * self.normal
+ #print 'deb:thic_vector: ', t_vector #---------------------
+ points.extend([v + t_vector for v in points])
+ faces = [[0+n, 1+n, 3+n, 2+n]]
+ self.drawtype = 'Mesh'
+ else:
+ edges = [[0+n, 1+n]]
+ vn +=2
+ return((points, edges, faces, vn))
+
+# class CMLine(CEntity):
+# 10 : 'start_point.x', 20 : 'start_point.y', 30 : 'start_point.z',
+# 11 : ['new_vertex(data)'], 21 : 'vertex.y', 31 : 'vertex.z',
+# 12 : ['new_seg_dir(data)'], 22 : 'seg_dir.y', 32 : 'seg_dir.z',
+# 13 : ['new_miter_dir(data)'], 23 : 'miter_dir.y', 33 : 'miter_dir.z',
+# 40 : 'scale', 41 : 'elem_param', 42 : 'fill_param',
+# 70 : 'justification', 71 : 'flags'
+# 72 : 'numverts', 73 : 'numelems', 74 : 'numparam', 75 : 'numfills',
+# 340 : 'id'
+#
+
+class CMLine(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'MLINE', None)
+ self.start_point = Vector()
+ self.vertex = None
+ self.seg_dir = None
+ self.miter_dir = None
+ self.verts = []
+ self.seg_dirs = []
+ self.miter_dirs = []
+ self.scale = 1.0
+ self.elem_param = 0
+ self.fill_param = 0
+ self.justification = 0
+ self.flags = 0
+ self.numverts = 0
+ self.numelems = 0
+ self.numparam = 0
+ self.numfills = 0
+ self.id = 0
+ self.normal = Vector((0,0,1))
+
+ def new_vertex(self, data):
+ self.vertex = Vector()
+ self.vertex.x = data
+ self.verts.append(self.vertex)
+
+ def new_seg_dir(self, data):
+ self.seg_dir = Vector()
+ self.seg_dir.x = data
+ self.seg_dirs.append(self.seg_dir)
+
+ def new_miter_dir(self, data):
+ self.miter_dir = Vector()
+ self.miter_dir.x = data
+ self.miter_dirs.append(self.miter_dir)
+
+
+
+#
+# class CMText(CText):
+# 1 : 'text', 3: 'more_text', 7 : 'style',
+# 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+# 11 : 'alignment_point.x', 21 : 'alignment_point.y', 31 : 'alignment_point.z',
+# 40 : 'nominal_height', 41 : 'reference_width', 42: 'width', 43 : 'height', 44 : 'line_spacing',
+# 50 : 'rotation_angle',
+# 71 : 'attachment_point', 72 : 'drawing_direction', 73 : 'spacing_style',
+#
+
+class CMText(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'MTEXT', 'Text')
+ self.text = ""
+ self.more_text = ""
+ self.style = ""
+ self.insertion_point = Vector()
+ self.alignment_point = Vector()
+ self.nominal_height = 1.0
+ self.reference_width = 1.0
+ self.width = 1.0
+ self.height = 1.0
+ self.rotation_angle = 0.0
+ self.attachment_point = 0
+ self.drawing_direction = 0
+ self.spacing_style = 0
+ self.normal = Vector((0,0,1))
+
+ def display(self):
+ CEntity.display(self)
+ print("%s %s" % (self.text, self.style))
+ print('MTEXTinsertion_point=',self.insertion_point)
+ print('MTEXTalignment_point=',self.alignment_point)
+
+ def draw(self):
+ drawText(self.text, self.insertion_point, self.height, self.width, self.rotation_angle, 0.0, self.normal)
+ return
+
+#
+# class CPoint(CEntity):
+# 10 : 'point.x', 20 : 'point.y', 30 : 'point.z',
+# 39 : 'thickness', 50 : 'orientation'
+#
+
+class CPoint(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'POINT', 'Mesh')
+ self.point = Vector()
+ self.thickness = 0.0
+ self.orientation = 0.0
+
+ def display(self):
+ CEntity.display(self)
+ print(self.point)
+ print("%.4f" % self.orientation)
+
+ def build(self, vn=0):
+ # draw as mesh-vertex
+ verts = [self.point]
+ return((verts, [], [], vn+1))
+
+ def draw(self):
+ #todo
+ # draw as empty-object
+ loc = self.point
+ #bpy.ops.object.new('DXFpoint')
+
+#
+# class CPolyLine(CEntity):
+# 1 : 'verts_follow', 2 : 'name',
+# 10 : 'elevation.x', 20 : 'elevation.y', 30 : 'elevation.z',
+# 40 : 'start_width', 41 : 'end_width',
+# 66 : 'verts_follow_flag',
+# 70 : 'flags', 71 : 'row_count', 72 : 'column_count',
+# 73 : 'row_density', 74 : 'column_density', 75 : 'linetype',
+#
+
+class CPolyLine(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'POLYLINE', 'Mesh')
+ self.verts = []
+ self.verts_follow = 1
+ self.name = ""
+ self.elevation = Vector()
+ self.thickness = 0.0
+ self.start_width = 0.0
+ self.end_width = 0.0
+ self.verts_follow_flags = 0
+ self.flags = 0
+ self.row_count = 1
+ self.column_count = 1
+ self.row_density = 1.0
+ self.column_density = 1.0
+ self.linetype = 1
+ self.normal = Vector((0,0,1))
+
+ def display(self):
+ CEntity.display(self)
+ print("VERTS")
+ for v in self.verts:
+ print(v.location)
+ print("END VERTS")
+
+ def build(self, vn=0):
+ verts = []
+ lines = []
+ v_start = vn
+ for vert in self.verts:
+ verts.append(vert.location)
+ lines.append((vn, vn+1))
+ vn += 1
+ if self.flags & PL_CLOSED:
+ lines[-1] = (vn-1, v_start)
+ else:
+ lines.pop()
+ if self.normal!=Vector((0,0,1)):
+ ma = getOCS(self.normal)
+ if ma:
+ verts = [v * ma for v in verts]
+ return((verts, lines, [], vn-1))
+
+#
+# class CShape(CEntity):
+# 2 : 'name',
+# 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+# 39 : 'thickness',
+# 40 : 'size', 41 : 'x_scale',
+# 50 : 'rotation_angle', 51 : 'oblique_angle',
+#
+
+class CShape(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'SHAPE', None)
+ self.name = ""
+ self.insertion_point = Vector()
+ self.thickness = 0.0
+ self.size = 1.0
+ self.x_scale = 1.0
+ self.rotation_angle = 0.0
+ self.oblique_angle = 0.0
+
+ def display(self):
+ CEntity.display(self)
+ print("%s" % (self.name))
+ print(self.insertion_point)
+
+#
+# class CSpline(CEntity):
+# 10 : ['new_control_point(data)'], 20 : 'control_point.y', 30 : 'control_point.z',
+# 11 : ['new_fit_point(data)'], 21 : 'fit_point.y', 31 : 'fit_point.z',
+# 40 : ['new_knot_value(data)'],
+# 12 : 'start_tangent.x', 22 : 'start_tangent.y', 32 : 'start_tangent.z',
+# 13 : 'end_tangent.x', 23 : 'end_tangent.y', 33 : 'end_tangent.z',
+# 41 : 'weight', 42 : 'knot_tol', 43 : 'control_point_tol', 44 : 'fit_tol',
+# 70 : 'flag', 71 : 'degree',
+# 72 : 'num_knots', 73 : 'num_control_points', 74 : 'num_fit_points',
+# 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+#
+
+class CSpline(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'SPLINE', 'Mesh')
+ self.control_points = []
+ self.fit_points = []
+ self.knot_values = []
+ self.control_point = None
+ self.fit_point = None
+ self.knot_value = None
+ self.start_tangent = Vector()
+ self.end_tangent = Vector()
+ self.weight = 1.0
+ self.knot_tol = 1e-6
+ self.control_point_tol = 1e-6
+ self.fit_tol = 1e-6
+ self.flag = 0
+ self.degree = 3
+ self.num_knots = 0
+ self.num_control_points = 0
+ self.num_fit_points = 0
+ self.thickness = 0.0
+ self.normal = Vector((0,0,1))
+
+ def new_control_point(self, data):
+ self.control_point = Vector()
+ self.control_point.x = data
+ self.control_points.append(self.control_point)
+
+ def new_fit_point(self, data):
+ self.fit_point = Vector()
+ self.fit_point.x = data
+ self.fit_points.append(self.fit_point)
+
+ def new_knot_value(self, data):
+ self.knot_value = data
+ self.knot_values.append(self.knot_value)
+
+ def display(self):
+ #not testet yet (migius)
+ CEntity.display(self)
+ print("CONTROL")
+ for p in self.control_points:
+ print(p)
+ print("FIT")
+ for p in self.fit_points:
+ print(p)
+ print("KNOT")
+ for v in self.knot_values:
+ print(v)
+
+ def build(self, vn=0):
+ verts = []
+ lines = []
+ for vert in self.control_points:
+ verts.append(vert)
+ lines.append((vn, vn+1))
+ vn += 1
+ lines.pop()
+ return((verts, lines, [], vn))
+
+
+#
+# class CSolid(CEntity):
+# 10 : 'point0.x', 20 : 'point0.y', 30 : 'point0.z',
+# 11 : 'point1.x', 21 : 'point1.y', 31 : 'point1.z',
+# 12 : 'point2.x', 22 : 'point2.y', 32 : 'point2.z',
+# 13 : 'point3.x', 23 : 'point3.y', 33 : 'point3.z',
+# 39 : 'thickness',
+#
+
+class CSolid(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'SOLID', 'Mesh')
+ self.point0 = Vector()
+ self.point1 = Vector()
+ self.point2 = Vector()
+ self.point3 = Vector()
+ self.normal = Vector((0,0,1))
+ self.thickness = 0.0
+
+ def display(self):
+ CEntity.display(self)
+ print(self.point0)
+ print(self.point1)
+ print(self.point2)
+ print(self.point3)
+
+ def build(self, vn=0):
+ points, edges, faces = [],[],[]
+ if self.point2 == self.point3:
+ points = [self.point0, self.point1, self.point2]
+ else:
+ points = [self.point0, self.point1, self.point2, self.point3]
+ pn = len(points)
+ v0 = vn
+
+ thic = self.thickness
+ t_vector = Vector((0, 0, thic))
+ if thic != 0 and (toggle & T_ThicON):
+ thic_points = [v + t_vector for v in points]
+ if thic < 0.0:
+ thic_points.extend(points)
+ points = thic_points
+ else:
+ points.extend(thic_points)
+
+ if pn == 4:
+ faces = [[0,1,3,2], [4,6,7,5], [0,4,5,1],
+ [1,5,7,3], [3,7,6,2], [2,6,4,0]]
+ elif pn == 3:
+ faces = [[0,1,2], [3,5,4], [0,3,4,1], [1,4,5,2], [2,5,3,0]]
+ elif pn == 2: faces = [[0,1,3,2]]
+ vn += 2*pn
+ else:
+ if pn == 4: faces = [[0,2,3,1]]
+ elif pn == 3: faces = [[0,2,1]]
+ elif pn == 2:
+ edges = [[0,1]]
+ self.drawtype = 'Mesh'
+ vn += pn
+ if self.normal!=Vector((0,0,1)):
+ ma = getOCS(self.normal)
+ if ma:
+ points = [v * ma for v in points]
+ return((points, edges, faces, vn))
+
+#
+# class CText(CEntity):
+# 1 : 'text', 7 : 'style',
+# 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+# 11 : 'alignment_point.x', 21 : 'alignment_point.y', 31 : 'alignment_point.z',
+# 40 : 'height', 41 : 'x_scale',
+# 50 : 'rotation_angle', 51 : 'oblique_angle',
+# 71 : 'flags', 72 : 'horizontal_justification', 73 : 'vertical_justification',
+#
+
+class CText(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'TEXT', 'Text')
+ self.text = ""
+ self.style = ""
+ self.insertion_point = Vector()
+ self.alignment_point = Vector()
+ self.height = 1.0
+ self.x_scale = 1.0
+ self.rotation_angle = 0.0
+ self.oblique_angle = 0.0
+ self.flags = 0
+ self.horizontal_justification = 0.0
+ self.vertical_justification = 0.0
+ self.thickness = 0.0
+ self.normal = Vector((0,0,1))
+
+ def display(self):
+ CEntity.display(self)
+ print("%s %s" % (self.text, self.style))
+ print(self.insertion_point)
+ print(self.alignment_point)
+
+ def draw(self):
+ drawText(self.text, self.insertion_point, self.height, self.x_scale, self.rotation_angle, self.oblique_angle, self.normal)
+ return
+
+
+def drawText(text, loc, size, spacing, angle, shear, normal=Vector((0,0,1))):
+ #print('angle_deg=',angle)
+ bpy.ops.object.text_add(
+ view_align=False,
+ enter_editmode=False,
+ location= loc,
+ #rotation=(0, 0, angle), #need radians here
+ )
+ cu = bpy.context.object.data
+ cu.body = text
+ cu.size = size #up 2.56
+ cu.space_word = spacing #up 2.56
+ cu.shear = shear
+ if angle!=0.0 or normal!=Vector((0,0,1)):
+ obj = bpy.context.object
+ transform(normal, angle, obj)
+ return
+
+#
+# class CTolerance(CEntity):
+# 3 : 'style',
+# 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+# 11 : 'direction.x', 21 : 'direction.y', 31 : 'direction.z',
+#
+
+class CTolerance(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'TOLERANCE', None)
+ self.stype = ""
+ self.insertion_point = Vector()
+ self.direction = Vector()
+
+#
+# class CTrace(CEntity):
+# 10 : 'point0.x', 20 : 'point0.y', 30 : 'point0.z',
+# 11 : 'point1.x', 21 : 'point1.y', 31 : 'point1.z',
+# 12 : 'point2.x', 22 : 'point2.y', 32 : 'point2.z',
+# 13 : 'point3.x', 23 : 'point3.y', 33 : 'point3.z',
+# 39 : 'thickness',
+#
+
+class CTrace(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'TRACE', 'Mesh')
+ self.point0 = Vector()
+ self.point1 = Vector()
+ self.point2 = Vector()
+ self.point3 = Vector()
+ self.normal = Vector((0,0,1))
+ self.thickness = 0.0
+
+ def display(self):
+ CEntity.display(self)
+ print(self.point0)
+ print(self.point1)
+ print(self.point2)
+ print(self.point3)
+
+ def build(self, vn=0):
+ points, edges, faces = [],[],[]
+ if self.point2 == self.point3:
+ points = [self.point0, self.point2, self.point1]
+ else:
+ points = [self.point0, self.point2, self.point1, self.point3]
+ pn = len(points)
+ v0 = vn
+ thic = self.thickness
+ t_vector = Vector((0, 0, thic))
+ if thic != 0 and (toggle & T_ThicON):
+ thic_points = [v + t_vector for v in points]
+ if thic < 0.0:
+ thic_points.extend(points)
+ points = thic_points
+ else:
+ points.extend(thic_points)
+
+ if pn == 4:
+ faces = [[0,1,3,2], [4,6,7,5], [0,4,5,1],
+ [1,5,7,3], [3,7,6,2], [2,6,4,0]]
+ elif pn == 3:
+ faces = [[0,1,2], [3,5,4], [0,3,4,1], [1,4,5,2], [2,5,3,0]]
+ elif pn == 2: faces = [[0,1,3,2]]
+ vn += 2*pn
+ else:
+ if pn == 4: faces = [[0,2,3,1]]
+ elif pn == 3: faces = [[0,2,1]]
+ elif pn == 2:
+ edges = [[0,1]]
+ self.drawtype = 'Mesh'
+ if self.normal!=Vector((0,0,1)):
+ ma = getOCS(self.normal)
+ if ma:
+ points = [v * ma for v in points]
+ return ((points, edges, faces, vn))
+
+#
+# class CVertex(CEntity):
+# 10 : 'location.x', 20 : 'location.y', 30 : 'location.z',
+# 40 : 'start_width', 41 : 'end_width', 42 : 'bulge',
+# 50 : 'tangent',
+# 70 : 'flags',
+# 71 : 'index1', 72 : 'index2', 73 : 'index3', 74 : 'index4',
+#
+
+class CVertex(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'VERTEX', None)
+ self.location = Vector()
+ self.start_width = 0.0
+ self.end_width = 0.0
+ self.bulge = 0.0
+ self.tangent = 0.0
+ self.flags = 0
+
+ def display(self):
+ return
+
+ def draw(self):
+ return
+
+#
+# class CViewPort(CEntity):
+# 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+# 12 : 'view_center.x', 22 : 'view_center.y', 32 : 'view_center.z',
+# 13 : 'snap_base.x', 23 : 'snap_base.y', 33 : 'snap_base.z',
+# 14 : 'snap_spacing.x', 24 : 'snap_spacing.y', 34 : 'snap_spacing.z',
+# 15 : 'grid_spacing.x', 25 : 'grid_spacing.y', 35 : 'grid_spacing.z',
+# 16 : 'view_direction.x', 26 : 'view_direction.y', 36 : 'view_direction.z',
+# 40 : 'width', 41 : 'height',
+# 68 : 'status', 69 : 'id',
+#
+
+class CViewPort(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'VIEWPORT', None)
+ self.center = Vector()
+ self.view_center = Vector()
+ self.snap_base = Vector()
+ self.snap_spacing = Vector()
+ self.grid_spacing = Vector()
+ self.view_direction = Vector()
+ self.width = 1.0
+ self.height = 1.0
+ self.status = 0
+ self.id = 0
+
+ def draw(self):
+ # Todo
+ return
+
+#
+# class CWipeOut(CEntity):
+# 10 : 'point.x', 20 : 'point.y', 30 : 'point.z',
+# 11 : 'direction.x', 21 : 'direction.y', 31 : 'direction.z',
+#
+
+class CWipeOut(CEntity):
+ def __init__(self):
+ CEntity.__init__(self, 'WIPEOUT', None)
+ self.point = Vector()
+ self.direction = Vector()
+
+#
+#
+#
+WORLDX = Vector((1.0,0.0,0.0))
+WORLDY = Vector((0.0,1.0,0.0))
+WORLDZ = Vector((0.0,0.0,1.0))
+
+
+def getOCS(az): #-----------------------------------------------------------------
+ """An implimentation of the Arbitrary Axis Algorithm.
+ """
+ #decide if we need to transform our coords
+ #if az[0] == 0 and az[1] == 0:
+ if abs(az.x) < 0.00001 and abs(az.y) < 0.00001:
+ if az.z > 0.0:
+ return False
+ elif az.z < 0.0:
+ return Matrix(-WORLDX, WORLDY*1, -WORLDZ)
+
+ cap = 0.015625 # square polar cap value (1/64.0)
+ if abs(az.x) < cap and abs(az.y) < cap:
+ ax = WORLDY.cross(az)
+ else:
+ ax = WORLDZ.cross(az)
+ ax = ax.normalize()
+ ay = az.cross(ax)
+ ay = ay.normalize()
+ return Matrix(ax, ay, az)
+
+
+
+def transform(normal, rotation, obj): #--------------------------------------------
+ """Use the calculated ocs to determine the objects location/orientation in space.
+ """
+ ma = Matrix([1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1])
+ o = Vector(obj.location)
+ ma_new = getOCS(normal)
+ if ma_new:
+ ma = ma_new.resize4x4()
+ o = o * ma #.copy().invert()
+
+ if rotation != 0:
+ g = radians(-rotation)
+ rmat = Matrix([cos(g), -sin(g), 0], [sin(g), cos(g), 0], [0, 0, 1])
+ ma = ma * rmat.resize4x4()
+
+ obj.matrix_world = ma #must be matrix4x4
+ obj.location = o
+
+
+DxfEntityAttributes = {
+'3DFACE' : {
+ 10 : 'point0.x', 20 : 'point0.y', 30 : 'point0.z',
+ 11 : 'point1.x', 21 : 'point1.y', 31 : 'point1.z',
+ 12 : 'point2.x', 22 : 'point2.y', 32 : 'point2.z',
+ 13 : 'point3.x', 23 : 'point3.y', 33 : 'point3.z',
+ 70 : 'flags',
+ },
+
+'3DSOLID' : {
+ 1 : 'data', 3 : 'more', 70 : 'version',
+ },
+
+'ACAD_PROXY_ENTITY' : {
+ 70 : 'format',
+ 90 : 'id', 91 : 'class', 92 : 'graphics_size', 93 : 'entity_size', 95: 'format',
+ 310 : 'data', 330 : 'id1', 340 : 'id2', 350 : 'id3', 360 : 'id4',
+ },
+
+'ARC' : {
+ 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+ 40 : 'radius',
+ 50 : 'start_angle', 51 : 'end_angle',
+ 39 : 'thickness',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'ARCALIGNEDTEXT' : {
+ 1 : 'text', 2 : 'font', 3 : 'bigfont', 7 : 'style',
+ 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+ 40 : 'radius', 41 : 'width', 42 : 'height', 43 : 'spacing',
+ 44 : 'offset', 45 : 'right_offset', 46 : 'left_offset',
+ 50 : 'start_angle', 51 : 'end_angle',
+ 70 : 'order', 71 : 'direction', 72 : 'alignment', 73 : 'side',
+ 74 : 'bold', 75 : 'italic', 76 : 'underline',
+ 77 : 'character_set', 78 : 'pitch', 79 : 'fonttype',
+ 90 : 'color',
+ 280 : 'wizard', 330 : 'id'
+ },
+
+'ATTDEF' : {
+ 1 : 'text', 2 : 'tag', 3 : 'prompt', 7 : 'style',
+ 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+ 11 : 'alignment_point.x', 21 : 'alignment_point.y', 31 : 'alignment_point.z',
+ 40 : 'height', 41 : 'x_scale',
+ 50 : 'rotation_angle', 51 : 'oblique_angle',
+ 70 : 'flags', 71 : 'text_generation_flags',
+ 72 : 'horizontal_justification', 74 : 'vertical_justification',
+ },
+
+
+'ATTRIB' : {
+ 1 : 'text', 2 : 'tag', 3 : 'prompt', 7 : 'style',
+ 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+ 11 : 'alignment_point.x', 21 : 'alignment_point.y', 31 : 'alignment_point.z',
+ 40 : 'height', 41 : 'x_scale',
+ 50 : 'rotation_angle', 51 : 'oblique_angle',
+ 70 : 'flags', 73 : 'length',
+ 71 : 'text_generation_flags', 72 : 'horizontal_justification', 74 : 'vertical_justification',
+ },
+
+'BLOCK' : {
+ 1 : 'xref', 2 : 'name', 3 : 'also_name',
+ 10 : 'base_point.x', 20 : 'base_point.y', 30 : 'base_point.z',
+ 40 : 'size', 41 : 'x_scale',
+ 50 : 'rotation_angle', 51 : 'oblique_angle',
+ 70 : 'flags',
+ },
+
+'CIRCLE' : {
+ 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+ 40 : 'radius',
+ 39 : 'thickness',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'DIMENSION' : {
+ 1 : 'text', 2 : 'name', 3 : 'style',
+ 10 : 'def_point.x', 20 : 'def_point.y', 30 : 'def_point.z',
+ 11 : 'mid_point.x', 21 : 'mid_point.y', 31 : 'mid_point.z',
+ 12 : 'vector.x', 22 : 'vector.y', 32 : 'vector.z',
+ 13 : 'def_point2.x', 23 : 'def_point2.y', 33 : 'def_point2.z',
+ 14 : 'vector2.x', 24 : 'vector2.y', 34 : 'vector2.z',
+ 15 : 'vector3.x', 25 : 'vector3.y', 35 : 'vector3.z',
+ 16 : 'vector4.x', 26 : 'vector4.y', 36 : 'vector4.z',
+ 70 : 'dimtype',
+ },
+
+'ELLIPSE' : {
+ 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+ 11 : 'end_point.x', 21 : 'end_point.y', 31 : 'end_point.z',
+ 40 : 'ratio', 41 : 'start', 42 : 'end',
+ 39 : 'thickness',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'HATCH' : {
+ 2 : 'pattern',
+ 10 : 'point.x', 20 : 'point.y', 30 : 'point.z',
+ 41 : 'scale', 47 : 'pixelsize', 52 : 'angle',
+ 70 : 'fill', 71 : 'associativity', 75: 'style', 77 : 'double',
+ 78 : 'numlines', 91 : 'numpaths', 98 : 'numseeds',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'IMAGE' : {
+ 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+ 11 : 'u_vector.x', 21 : 'u_vector.y', 31 : 'u_vector.z',
+ 12 : 'v_vector.x', 22 : 'v_vector.y', 32 : 'v_vector.z',
+ 13 : 'size.x', 23 : 'size.y', 33 : 'size.z',
+ 14 : 'clip.x', 24 : 'clip.y', 34 : 'clip.z',
+ 70 : 'display', 71 : 'cliptype',
+ 90 : 'version',
+ 280 : 'clipstate', 281 : 'brightness', 282 : 'contrast', 283 : 'fade',
+ 340 : 'image', 360 : 'reactor',
+ },
+
+'INSERT' : {
+ 1 : 'attributes_follow', 2 : 'name',
+ 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+ 41 : 'x_scale', 42 : 'y_scale', 43 : 'z_scale',
+ 44 : 'column_spacing', 45 : 'row_spacing',
+ 50 : 'rotation_angle', 66 : 'attributes_follow',
+ 70 : 'column_count', 71 : 'row_count',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'LEADER' : {
+ 3 : 'style',
+ 10 : ['new_vertex(data)'], 20 : 'vertex.y', 30 : 'vertex.z',
+ 40 : 'height', 41 : 'width',
+ 71 : 'arrowhead', 72 : 'pathtype', 73 : 'creation',
+ 74 : 'hookdir', 75 : 'hookline', 76 : 'numverts', 77 : 'color',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ 211 : 'horizon.x', 221 : 'horizon.y', 231 : 'horizon.z',
+ 212 : 'offset_ins.x', 222 : 'offset_ins.y', 232 : 'offset_ins.z',
+ 213 : 'offset_ann.x', 223 : 'offset_ann.y', 233 : 'offset_ann.z',
+ },
+
+'LINE' : {
+ 10 : 'start_point.x', 20 : 'start_point.y', 30 : 'start_point.z',
+ 11 : 'end_point.x', 21 : 'end_point.y', 31 : 'end_point.z',
+ 39 : 'thickness',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'LWPOLYLINE' : {
+ 10 : ['new_vertex(data)'], 20 : 'vertex.y', 30 : 'vertex.z',
+ 38 : 'elevation', 39 : 'thickness',
+ 40 : 'start_width', 41 : 'end_width', 42 : 'bulge', 43 : 'constant_width',
+ 70 : 'flags', 90 : 'numverts',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'MLINE' : {
+ 10 : 'start_point.x', 20 : 'start_point.y', 30 : 'start_point.z',
+ 11 : ['new_vertex(data)'], 21 : 'vertex.y', 31 : 'vertex.z',
+ 12 : ['new_seg_dir(data)'], 22 : 'seg_dir.y', 32 : 'seg_dir.z',
+ 13 : ['new_miter_dir(data)'], 23 : 'miter_dir.y', 33 : 'miter_dir.z',
+ 39 : 'thickness',
+ 40 : 'scale', 41 : 'elem_param', 42 : 'fill_param',
+ 70 : 'justification', 71 : 'flags',
+ 72 : 'numverts', 73 : 'numelems', 74 : 'numparam', 75 : 'numfills',
+ 340 : 'id',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'MTEXT' : {
+ 1 : 'text', 3: 'more_text', 7 : 'style',
+ 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+ 11 : 'alignment_point.x', 21 : 'alignment_point.y', 31 : 'alignment_point.z',
+ 40 : 'nominal_height', 41 : 'reference_width', 42: 'width', 43 : 'height', 44 : 'line_spacing',
+ 50 : 'rotation_angle',
+ 71 : 'attachment_point', 72 : 'drawing_direction', 73 : 'spacing_style',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'POINT' : {
+ 10 : 'point.x', 20 : 'point.y', 30 : 'point.z',
+ 39 : 'thickness', 50 : 'orientation',
+ },
+
+'POLYLINE' : {
+ 1 : 'verts_follow', 2 : 'name',
+ 10 : 'elevation.x', 20 : 'elevation.y', 30 : 'elevation.z',
+ 39 : 'thickness',
+ 40 : 'start_width', 41 : 'end_width',
+ 66 : 'verts_follow_flag',
+ 70 : 'flags', 71 : 'row_count', 72 : 'column_count',
+ 73 : 'row_density', 74 : 'column_density', 75 : 'linetype',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'RAY' : {
+ 10 : 'point.x', 20 : 'point.y', 30 : 'point.z',
+ 11 : 'direction.x', 21 : 'direction.y', 31 : 'direction.z',
+ },
+
+'RTEXT' : {
+ 1 : 'text', 7 : 'style',
+ 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+ 39 : 'thickness',
+ 40 : 'height',
+ 50 : 'rotation_angle',
+ 70 : 'flags',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'SHAPE' : {
+ 2 : 'name',
+ 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+ 39 : 'thickness',
+ 40 : 'size', 41 : 'x_scale',
+ 50 : 'rotation_angle', 51 : 'oblique_angle',
+ 39 : 'thickness',
+ },
+
+'SOLID' : {
+ 10 : 'point0.x', 20 : 'point0.y', 30 : 'point0.z',
+ 11 : 'point1.x', 21 : 'point1.y', 31 : 'point1.z',
+ 12 : 'point2.x', 22 : 'point2.y', 32 : 'point2.z',
+ 13 : 'point3.x', 23 : 'point3.y', 33 : 'point3.z',
+ 39 : 'thickness',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'SPLINE' : {
+ 10 : ['new_control_point(data)'], 20 : 'control_point.y', 30 : 'control_point.z',
+ 11 : ['new_fit_point(data)'], 21 : 'fit_point.y', 31 : 'fit_point.z',
+ 40 : ['new_knot_value(data)'],
+ 12 : 'start_tangent.x', 22 : 'start_tangent.y', 32 : 'start_tangent.z',
+ 13 : 'end_tangent.x', 23 : 'end_tangent.y', 33 : 'end_tangent.z',
+ 39 : 'thickness',
+ 41 : 'weight', 42 : 'knot_tol', 43 : 'control_point_tol', 44 : 'fit_tol',
+ 70 : 'flag', 71 : 'degree',
+ 72 : 'num_knots', 73 : 'num_control_points', 74 : 'num_fit_points',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'TEXT' : {
+ 1 : 'text', 7 : 'style',
+ 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+ 11 : 'alignment_point.x', 21 : 'alignment_point.y', 31 : 'alignment_point.z',
+ 40 : 'height', 41 : 'x_scale',
+ 50 : 'rotation_angle', 51 : 'oblique_angle',
+ 71 : 'flags', 72 : 'horizontal_justification', 73 : 'vertical_justification',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'TOLERANCE' : {
+ 3 : 'style',
+ 10 : 'insertion_point.x', 20 : 'insertion_point.y', 30 : 'insertion_point.z',
+ 11 : 'direction.x', 21 : 'direction.y', 31 : 'direction.z',
+ },
+
+'TRACE' : {
+ 10 : 'point0.x', 20 : 'point0.y', 30 : 'point0.z',
+ 11 : 'point1.x', 21 : 'point1.y', 31 : 'point1.z',
+ 12 : 'point2.x', 22 : 'point2.y', 32 : 'point2.z',
+ 13 : 'point3.x', 23 : 'point3.y', 33 : 'point3.z',
+ 39 : 'thickness',
+ 210 : 'normal.x', 220 : 'normal.y', 230 : 'normal.z',
+ },
+
+'VERTEX' : {
+ 10 : 'location.x', 20 : 'location.y', 30 : 'location.z',
+ 40 : 'start_width', 41 : 'end_width', 42 : 'bulge',
+ 50 : 'tangent',
+ 70 : 'flags',
+ 71 : 'index1', 72 : 'index2', 73 : 'index3', 74 : 'index4',
+ },
+
+'VIEWPORT' : {
+ 10 : 'center.x', 20 : 'center.y', 30 : 'center.z',
+ 12 : 'view_center.x', 22 : 'view_center.y', 32 : 'view_center.z',
+ 13 : 'snap_base.x', 23 : 'snap_base.y', 33 : 'snap_base.z',
+ 14 : 'snap_spacing.x', 24 : 'snap_spacing.y', 34 : 'snap_spacing.z',
+ 15 : 'grid_spacing.x', 25 : 'grid_spacing.y', 35 : 'grid_spacing.z',
+ 16 : 'view_direction.x', 26 : 'view_direction.y', 36 : 'view_direction.z',
+ 40 : 'width', 41 : 'height',
+ 68 : 'status', 69 : 'id',
+ },
+
+'WIPEOUT' : {
+ 10 : 'point.x', 20 : 'point.y', 30 : 'point.z',
+ 11 : 'direction.x', 21 : 'direction.y', 31 : 'direction.z',
+ },
+
+}
+
+
+#
+# Flags
+#
+
+# Polyline flags
+PL_CLOSED = 0x01
+PL_CURVE_FIT_VERTS = 0x02
+PL_SPLINE_FIT_VERTS = 0x04
+PL_3D_POLYLINE = 0x08
+PL_3D_POLYGON_MESH = 0x10
+PL_CLOSED_IN_N_DIR = 0x20
+PL_POLYFACE_MESH = 0x40
+PL_CONTINUOUS = 0x80
+
+
+# Vertex flags
+VX_EXTRA_FLAG_CREATED = 0x01
+VX_CURVE_FIT_TANGENT_DEFINED = 0x02
+VX_SPLINE_VERTEX_CREATED = 0x08
+VX_SPLINE_FRAME_CONTROL_POINT = 0x10
+VX_3D_POLYLINE_VERTEX = 0x20
+VX_3D_POLYGON_MESH_VERTEX = 0x40
+VX_POLYFACE_MESH_VERTEX = 0x80
+
+# 3DFACE flags
+
+F3D_EDGE0_INVISIBLE = 0x01
+F3D_EDGE1_INVISIBLE = 0x02
+F3D_EDGE2_INVISIBLE = 0x04
+F3D_EDGE3_INVISIBLE = 0x08
+
+#
+# readDxfFile(filePath):
+#
+
+def readDxfFile(fileName):
+ global toggle, theCodec
+
+ print( "Opening DXF file "+ fileName )
+
+ # fp= open(fileName, "rU")
+ fp = codecs.open(fileName, "r", encoding=theCodec)
+ first = True
+ statements = []
+ no = 0
+ for line in fp:
+ word = line.strip()
+ no += 1
+ if first:
+ if word:
+ code = int(word)
+ first = False
+ else:
+ if toggle & T_Verbose:
+ print("%4d: %4d %s" % (no, code, word))
+ if code < 10:
+ data = word
+ elif code < 60:
+ data = float(word)
+ elif code < 100:
+ data = int(word)
+ elif code < 140:
+ data = word
+ elif code < 150:
+ data = float(word)
+ elif code < 200:
+ data = int(word)
+ elif code < 300:
+ data = float(word)
+ elif code < 370:
+ data = word
+ elif code < 390:
+ data = int(word)
+ elif code < 400:
+ data = word
+ elif code < 410:
+ data = int(word)
+ elif code < 1010:
+ data = word
+ elif code < 1060:
+ data = float(word)
+ elif code < 1080:
+ data = int(word)
+
+ statements.append((code,data))
+ first = True
+ fp.close()
+
+ statements.reverse()
+ sections = {}
+ handles = {}
+ while statements:
+ (code,data) = statements.pop()
+ if code == 0:
+ if data == 'SECTION':
+ section = CSection()
+ elif code == 2:
+ section.type = data
+ if data == 'HEADER':
+ parseHeader(section, statements, handles)
+ known = False
+ elif data == 'CLASSES':
+ parseClasses(section, statements, handles)
+ known = False
+ elif data == 'TABLES':
+ parseTables(section, statements, handles)
+ known = False
+ elif data == 'BLOCKS':
+ parseBlocks(section, statements, handles)
+ known = False
+ elif data == 'ENTITIES':
+ parseEntities(section, statements, handles)
+ known = False
+ elif data == 'OBJECTS':
+ parseObjects(section, statements, handles)
+ sections[data] = section
+ elif code == 999:
+ pass
+ else:
+ raise NameError("Unexpected code in SECTION context: %d %s" % (code,data))
+
+ if toggle & T_Verbose:
+ for (typ,section) in sections.items():
+ section.display()
+ return sections
+
+
+#
+# 0
+# SECTION
+# 2
+# HEADER
+#
+# 9
+# $<variable>
+# <group code>
+# <value>
+#
+# 0
+# ENDSEC
+
+
+def parseHeader(section, statements, handles):
+ while statements:
+ (code,data) = statements.pop()
+ if code == 0:
+ if data == 'ENDSEC':
+ return
+
+ return
+
+
+# 0
+# SECTION
+# 2
+# CLASSES
+#
+# 0
+# CLASS
+# 1
+# <class dxf record>
+# 2
+# <class name>
+# 3
+# <app name>
+# 90
+# <flag>
+# 280
+# <flag>
+# 281
+# <flag>
+#
+# 0
+# ENDSEC
+
+def parseClasses(section, statements, handles):
+ while statements:
+ (code,data) = statements.pop()
+ if code == 0:
+ if data == 'ENDSEC':
+ return
+
+ return
+
+
+# 0
+# SECTION
+# 2
+# TABLES
+#
+# 0
+# TABLE
+# 2
+# <table type>
+# 5
+# <handle>
+# 100
+# AcDbSymbolTable
+# 70
+# <max. entries>
+#
+# 0
+# <table type>
+# 5
+# <handle>
+# 100
+# AcDbSymbolTableRecord
+# .
+# . <data>
+# .
+#
+# 0
+# ENDTAB
+#
+# 0
+# ENDSEC
+
+#
+# APPID (application identification table)
+#
+# BLOCK_RECORD (block reference table)
+#
+# DIMSTYLE (dimension style table)
+#
+# LAYER (layer table)
+#
+# LTYPE (linetype table)
+#
+# STYLE (text style table)
+#
+# UCS (User Coordinate System table)
+#
+# VIEW (view table)
+#
+# VPORT (viewport configuration table)
+
+
+def parseTables(section, statements, handles):
+ tables = []
+ section.data = tables
+ while statements:
+ (code,data) = statements.pop()
+ if code == 0:
+ if data == 'ENDSEC':
+ return
+ '''
+ known = False
+ elif data == 'TABLE':
+ table = CTable()
+ tables.append(table)
+ known = False
+ elif data == 'ENDTAB':
+ pass
+ known = False
+ elif data == table.type:
+ parseTableType
+ table = CTable()
+ tables.append(table)
+ table.type = word
+ elif code == 2:
+ table.type = word
+ elif code == 5:
+ table.handle = word
+ handles[word] = table
+ elif code == 330:
+ table.owner = word
+ elif code == 100:
+ table.subclass = word
+ elif code == 70:
+ table.nEntries = int(word)
+ '''
+ return
+
+# 0
+# SECTION
+# 2
+# BLOCKS
+#
+# 0
+# BLOCK
+# 5
+# <handle>
+# 100
+# AcDbEntity
+# 8
+# <layer>
+# 100
+# AcDbBlockBegin
+# 2
+# <block name>
+# 70
+# <flag>
+# 10
+# <X value>
+# 20
+# <Y value>
+# 30
+# <Z value>
+# 3
+# <block name>
+# 1
+# <xref path>
+#
+# 0
+# <entity type>
+# .
+# . <data>
+# .
+#
+# 0
+# ENDBLK
+# 5
+# <handle>
+# 100
+# AcDbBlockEnd
+#
+# 0
+# ENDSEC
+
+def parseBlocks(section, statements, handles):
+ while statements:
+ (code,data) = statements.pop()
+ if code == 0:
+ if data == 'ENDSEC':
+ return
+
+ return
+
+# 0
+# SECTION
+# 2
+# ENTITIES
+#
+# 0
+# <entity type>
+# 5
+# <handle>
+# 330
+# <pointer to owner>
+# 100
+# AcDbEntity
+# 8
+# <layer>
+# 100
+# AcDb<classname>
+# .
+# . <data>
+# .
+#
+# 0
+# ENDSEC
+
+Ignorables = ['DIMENSION', 'TEXT', 'VIEWPORT']
+
+ClassCreators = {
+ '3DFACE': 'C3dFace()',
+ '3DSOLID': 'C3dSolid()',
+ 'ACAD_PROXY_ENTITY': 'CAcadProxyEntity()',
+ 'ACAD_ZOMBIE_ENTITY': 0,
+ 'ARC': 'CArc()',
+ 'ARCALIGNEDTEXT': 'CArcAlignedText()',
+ 'ATTDEF': 'CAttdef()',
+ 'ATTRIB': 'CAttrib()',
+ 'BODY': 0,
+ 'CIRCLE': 'CCircle()',
+ 'DIMENSION': 'CDimension()',
+ 'ELLIPSE': 'CEllipse()',
+ 'HATCH': 'CHatch()',
+ 'IMAGE': 'CImage()',
+ 'INSERT': 'CInsert()',
+ 'LEADER': 'CLeader()',
+ 'LINE': 'CLine()',
+ 'LWPOLYLINE': 'CLWPolyLine()',
+ 'MLINE': 'CMLine()',
+ 'MTEXT': 'CMText()',
+ 'OLEFRAME': 0,
+ 'OLE2FRAME': 0,
+ 'POINT': 'CPoint()',
+ 'POLYLINE': 'CPolyLine()',
+ 'RAY': 'CRay()',
+ 'REGION': 0,
+ 'RTEXT': 'CRText',
+ 'SEQEND': 0,
+ 'SHAPE': 'CShape()',
+ 'SOLID': 'CSolid()',
+ 'SPLINE': 'CSpline()',
+ 'TEXT': 'CText()',
+ 'TOLERANCE': 'CTolerance()',
+ 'TRACE': 'CTrace()',
+ 'VERTEX': 'CVertex()',
+ 'VIEWPORT': 'CViewPort()',
+ 'WIPEOUT': 'CWipeOut()',
+ 'XLINE': 'CXLine()',
+}
+
+def parseEntities(section, statements, handles):
+ entities = []
+ section.data = entities
+ while statements:
+ (code,data) = statements.pop()
+ if toggle & T_Verbose:
+ print("ent", code,data)
+ if code == 0:
+ known = True
+ if data in Ignorables:
+ ignore = True
+ else:
+ ignore = False
+
+ try:
+ creator = ClassCreators[data]
+ except:
+ creator = None
+
+ if creator:
+ entity = eval(creator)
+ elif data == 'ENDSEC':
+ return
+ else:
+ known = False
+
+ if data == 'POLYLINE':
+ verts = entity.verts
+ elif data == 'VERTEX':
+ verts.append(entity)
+
+ if data == 'SEQEND':
+ attributes = []
+ known = False
+ elif creator == 0:
+ ignore = True
+ elif known:
+ entities.append(entity)
+ attributes = DxfEntityAttributes[data]
+ else:
+ raise NameError("Unknown data %s" % data)
+
+ elif not known:
+ pass
+ else:
+ expr = getAttribute(attributes, code)
+ if expr:
+ exec(expr)
+ else:
+ expr = getAttribute(DxfCommonAttributes, code)
+ if expr:
+ exec(expr)
+ elif code >= 1000 or ignore:
+ pass
+ elif toggle & T_Debug:
+ raise NameError("Unknown code %d for %s" % (code, entity.type))
+
+ return
+
+def getAttribute(attributes, code):
+ try:
+ ext = attributes[code]
+ if type(ext) == str:
+ expr = "entity.%s = data" % ext
+ else:
+ name = ext[0]
+ expr = "entity.%s" % name
+ except:
+ expr = None
+ return expr
+
+
+# 0
+# SECTION
+# 2
+# OBJECTS
+#
+# 0
+# DICTIONARY
+# 5
+# <handle>
+# 100
+# AcDbDictionary
+#
+# 3
+# <dictionary name>
+# 350
+# <handle of child>
+#
+# 0
+# <object type>
+# .
+# . <data>
+# .
+#
+# 0
+# ENDSEC
+
+def parseObjects(data, statements, handles):
+ while statements:
+ (code,data) = statements.pop()
+ if code == 0:
+ if data == 'ENDSEC':
+ return
+
+ return
+
+#
+# buildGeometry(entities):
+# addMesh(name, verts, edges, faces):
+#
+
+def buildGeometry(entities):
+ try: bpy.ops.object.mode_set(mode='OBJECT')
+ except: pass
+ v_verts = []
+ v_vn = 0
+ e_verts = []
+ e_edges = []
+ e_vn = 0
+ f_verts = []
+ f_edges = []
+ f_faces = []
+ f_vn = 0
+ for ent in entities:
+ if ent.drawtype in ('Mesh','Curve'):
+ (verts, edges, faces, vn) = ent.build()
+ if not toggle & T_DrawOne:
+ drawGeometry(verts, edges, faces)
+ else:
+ if verts:
+ if faces:
+ for i,f in enumerate(faces):
+ #print ('face=', f)
+ faces[i] = tuple(it+f_vn for it in f)
+ for i,e in enumerate(edges):
+ edges[i] = tuple(it+f_vn for it in e)
+ f_verts.extend(verts)
+ f_edges.extend(edges)
+ f_faces.extend(faces)
+ f_vn += len(verts)
+ elif edges:
+ for i,e in enumerate(edges):
+ edges[i] = tuple(it+e_vn for it in e)
+ e_verts.extend(verts)
+ e_edges.extend(edges)
+ e_vn += len(verts)
+ else:
+ v_verts.extend(verts)
+ v_vn += len(verts)
+ else:
+ ent.draw()
+
+ if toggle & T_DrawOne:
+ drawGeometry(f_verts, f_edges, f_faces)
+ drawGeometry(e_verts, e_edges)
+ drawGeometry(v_verts)
+
+
+
+def drawGeometry(verts, edges=[], faces=[]):
+ if verts:
+ if edges and (toggle & T_Curves):
+ print ('draw Curve')
+ cu = bpy.data.curves.new('DXFLines', 'CURVE')
+ cu.dimensions = '3D'
+ buildSplines(cu, verts, edges)
+ ob = addObject('DXFLines', cu)
+ else:
+ #for v in verts: print(v)
+ #print ('draw Mesh with %s vertices' %(len(verts)))
+ #for e in edges: print(e)
+ #print ('draw Mesh with %s edges' %(len(edges)))
+ #for f in faces: print(f)
+ #print ('draw Mesh with %s faces' %(len(faces)))
+ me = bpy.data.meshes.new('DXFmesh')
+ me.from_pydata(verts, edges, faces)
+ ob = addObject('DXFmesh', me)
+ removeDoubles(ob)
+ return
+
+
+
+def buildSplines(cu, verts, edges):
+ if edges:
+ point_list = []
+ (v0,v1) = edges.pop()
+ v1_old = v1
+ newPoints = [tuple(verts[v0]),tuple(verts[v1])]
+ for (v0,v1) in edges:
+ if v0==v1_old:
+ newPoints.append(tuple(verts[v1]))
+ else:
+ #print ('newPoints=', newPoints)
+ point_list.append(newPoints)
+ newPoints = [tuple(verts[v0]),tuple(verts[v1])]
+ v1_old = v1
+ point_list.append(newPoints)
+ for points in point_list:
+ #spline = cu.splines.new('BEZIER')
+ spline = cu.splines.new('POLY')
+ #spline.endpoint_u = True
+ #spline.order_u = 2
+ #spline.resolution_u = 1
+ #spline.bezier_points.add(2)
+
+ spline.points.add(len(points)-1)
+ #spline.points.foreach_set('co', points)
+ for i,p in enumerate(points):
+ spline.points[i].co = (p[0],p[1],p[2],0)
+
+ print ('spline.type=', spline.type)
+ print ('cu spline number=', len(cu.splines))
+
+
+def addObject(name, data):
+ ob = bpy.data.objects.new(name, data)
+ scn = bpy.context.scene
+ scn.objects.link(ob)
+ return ob
+
+
+def removeDoubles(ob):
+ global theMergeLimit
+ if toggle & T_Merge:
+ scn = bpy.context.scene
+ scn.objects.active = ob
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.remove_doubles(limit=theMergeLimit)
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+
+
+#
+# clearScene(context):
+#
+
+def clearScene():
+ global toggle
+ scn = bpy.context.scene
+ print("clearScene %s %s" % (toggle & T_NewScene, scn))
+ if not toggle & T_NewScene:
+ return scn
+
+ for ob in scn.objects:
+ if ob.type in ["MESH", "CURVE", "TEXT"]:
+ scn.objects.active = ob
+ bpy.ops.object.mode_set(mode='OBJECT')
+ scn.objects.unlink(ob)
+ del ob
+ return scn
+
+#
+# readAndBuildDxfFile(filepath):
+#
+
+def readAndBuildDxfFile(filepath):
+ fileName = os.path.expanduser(filepath)
+ if fileName:
+ (shortName, ext) = os.path.splitext(fileName)
+ #print("filepath: ", filepath)
+ #print("fileName: ", fileName)
+ #print("shortName: ", shortName)
+ if ext.lower() != ".dxf":
+ print("Error: Not a dxf file: " + fileName)
+ return
+ if toggle & T_NewScene:
+ clearScene()
+ if 0: # how to switch to the new scene?? (migius)
+ new_scn = bpy.data.scenes.new(shortName[-20:])
+ #new_scn.layers = (1<<20) -1
+ new_scn_name = new_scn.name
+ bpy.data.screens.scene = new_scn
+ #print("newScene: %s" % (new_scn))
+ sections = readDxfFile(fileName)
+ print("Building geometry")
+ buildGeometry(sections['ENTITIES'].data)
+ print("Done")
+ return
+ print("Error: Not a dxf file: " + filepath)
+ return
+
+#
+# User interface
+#
+
+DEBUG= False
+from bpy.props import *
+
+def tripleList(list1):
+ list3 = []
+ for elt in list1:
+ list3.append((elt,elt,elt))
+ return list3
+
+class IMPORT_OT_autocad_dxf(bpy.types.Operator):
+ '''Import from DXF file format (.dxf)'''
+ bl_idname = "import_scene.autocad_dxf"
+ bl_description = 'Import from DXF file format (.dxf)'
+ bl_label = "Import DXF" +' v.'+ __version__
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+
+ filepath = StringProperty(name="File Path", description="Filepath used for importing the DXF file", maxlen= 1024, default= "")
+
+ new_scene = BoolProperty(name="Replace scene", description="Replace scene", default=toggle&T_NewScene)
+ #new_scene = BoolProperty(name="New scene", description="Create new scene", default=toggle&T_NewScene)
+ curves = BoolProperty(name="Draw curves", description="Draw entities as curves", default=toggle&T_Curves)
+ thic_on = BoolProperty(name="Thic ON", description="Support THICKNESS", default=toggle&T_ThicON)
+
+ merge = BoolProperty(name="Remove doubles", description="Merge coincident vertices", default=toggle&T_Merge)
+ mergeLimit = FloatProperty(name="Limit", description="Merge limit", default = theMergeLimit*1e4,min=1.0, soft_min=1.0, max=100.0, soft_max=100.0)
+
+ draw_one = BoolProperty(name="Merge all", description="Draw all into one mesh-object", default=toggle&T_DrawOne)
+ circleResolution = IntProperty(name="Circle resolution", description="Circle/Arc are aproximated will this factor", default = theCircleRes,
+ min=4, soft_min=4, max=360, soft_max=360)
+ codecs = tripleList(['iso-8859-15', 'utf-8', 'ascii'])
+ codec = EnumProperty(name="Codec", description="Codec", items=codecs, default = 'ascii')
+
+ debug = BoolProperty(name="Debug", description="Unknown DXF-codes generate errors", default=toggle&T_Debug)
+ verbose = BoolProperty(name="Verbose", description="Print debug info", default=toggle&T_Verbose)
+
+ ##### DRAW #####
+ def draw(self, context):
+ layout0 = self.layout
+ #layout0.enabled = False
+
+ #col = layout0.column_flow(2,align=True)
+ layout = layout0.box()
+ col = layout.column()
+ #col.prop(self, 'KnotType') waits for more knottypes
+ #col.label(text="import Parameters")
+ #col.prop(self, 'replace')
+ col.prop(self, 'new_scene')
+
+ row = layout.row(align=True)
+ row.prop(self, 'curves')
+ row.prop(self, 'circleResolution')
+
+ row = layout.row(align=True)
+ row.prop(self, 'merge')
+ if self.merge:
+ row.prop(self, 'mergeLimit')
+
+ row = layout.row(align=True)
+ #row.label('na')
+ row.prop(self, 'draw_one')
+ row.prop(self, 'thic_on')
+
+ col = layout.column()
+ col.prop(self, 'codec')
+
+ row = layout.row(align=True)
+ row.prop(self, 'debug')
+ if self.debug:
+ row.prop(self, 'verbose')
+
+ def execute(self, context):
+ global toggle, theMergeLimit, theCodec, theCircleRes
+ O_Merge = T_Merge if self.properties.merge else 0
+ #O_Replace = T_Replace if self.properties.replace else 0
+ O_NewScene = T_NewScene if self.properties.new_scene else 0
+ O_Curves = T_Curves if self.properties.curves else 0
+ O_ThicON = T_ThicON if self.properties.thic_on else 0
+ O_DrawOne = T_DrawOne if self.properties.draw_one else 0
+ O_Debug = T_Debug if self.properties.debug else 0
+ O_Verbose = T_Verbose if self.properties.verbose else 0
+
+ toggle = O_Merge | O_DrawOne | O_NewScene | O_Curves | O_ThicON | O_Debug | O_Verbose
+ theMergeLimit = self.properties.mergeLimit*1e-4
+ theCircleRes = self.properties.circleResolution
+ theCodec = self.properties.codec
+
+ readAndBuildDxfFile(self.properties.filepath)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ wm.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+def menu_func(self, context):
+ self.layout.operator(IMPORT_OT_autocad_dxf.bl_idname, text="Autocad (.dxf)")
+
+def register():
+ bpy.types.INFO_MT_file_import.append(menu_func)
+
+def unregister():
+ bpy.types.INFO_MT_file_import.remove(menu_func)
+
+if __name__ == "__main__":
+ register()
+
+
diff --git a/io_import_scene_lwo.py b/io_import_scene_lwo.py
new file mode 100644
index 00000000..5b25226b
--- /dev/null
+++ b/io_import_scene_lwo.py
@@ -0,0 +1,1254 @@
+# ##### 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": "Import LightWave Objects",
+ "author": "Ken Nign (Ken9)",
+ "version": (1, 2),
+ "blender": (2, 5, 3),
+ "api": 31847,
+ "location": "File > Import > LightWave Object (.lwo)",
+ "description": "Imports a LWO file including any UV, Morph and Color maps. "\
+ "Can convert Skelegons to an Armature.",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/LightWave_Object",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=23623&group_id=153&atid=469",
+ "category": "Import-Export"}
+
+# Copyright (c) Ken Nign 2010
+# ken@virginpi.com
+#
+# Version 1.2 - Sep 7, 2010
+#
+# Loads a LightWave .lwo object file, including the vertex maps such as
+# UV, Morph, Color and Weight maps.
+#
+# Will optionally create an Armature from an embedded Skelegon rig.
+#
+# Point orders are maintained so that .mdds can exchanged with other
+# 3D programs.
+#
+#
+# Notes:
+# NGons, polygons with more than 4 points are supported, but are
+# added (as triangles) after the vertex maps have been applied. Thus they
+# won't contain all the vertex data that the original ngon had.
+#
+# Blender is limited to only 8 UV Texture and 8 Vertex Color maps,
+# thus only the first 8 of each can be imported.
+#
+# History:
+#
+# 1.2 Added Absolute Morph and CC Edge Weight support.
+# Made edge creation safer.
+# 1.0 First Release
+
+
+import os
+import io
+import time
+import struct
+import chunk
+
+import bpy
+import mathutils
+from mathutils.geometry import tesselate_polygon
+
+
+class _obj_layer(object):
+ __slots__ = (
+ "name",
+ "index",
+ "parent_index",
+ "pivot",
+ "pols",
+ "bones",
+ "bone_names",
+ "bone_rolls",
+ "pnts",
+ "wmaps",
+ "colmaps",
+ "uvmaps",
+ "morphs",
+ "edge_weights",
+ "surf_tags",
+ "has_subds",
+ )
+ def __init__(self):
+ self.name= ""
+ self.index= -1
+ self.parent_index= -1
+ self.pivot= [0, 0, 0]
+ self.pols= []
+ self.bones= []
+ self.bone_names= {}
+ self.bone_rolls= {}
+ self.pnts= []
+ self.wmaps= {}
+ self.colmaps= {}
+ self.uvmaps= {}
+ self.morphs= {}
+ self.edge_weights= {}
+ self.surf_tags= {}
+ self.has_subds= False
+
+
+class _obj_surf(object):
+ __slots__ = (
+ "bl_mat",
+ "name",
+ "source_name",
+ "colr",
+ "diff",
+ "lumi",
+ "spec",
+ "refl",
+ "rblr",
+ "tran",
+ "rind",
+ "tblr",
+ "trnl",
+ "glos",
+ "shrp",
+ "smooth",
+ )
+
+ def __init__(self):
+ self.bl_mat= None
+ self.name= "Default"
+ self.source_name= ""
+ self.colr= [1.0, 1.0, 1.0]
+ self.diff= 1.0 # Diffuse
+ self.lumi= 0.0 # Luminosity
+ self.spec= 0.0 # Specular
+ self.refl= 0.0 # Reflectivity
+ self.rblr= 0.0 # Reflection Bluring
+ self.tran= 0.0 # Transparency (the opposite of Blender's Alpha value)
+ self.rind= 1.0 # RT Transparency IOR
+ self.tblr= 0.0 # Refraction Bluring
+ self.trnl= 0.0 # Translucency
+ self.glos= 0.4 # Glossiness
+ self.shrp= 0.0 # Diffuse Sharpness
+ self.smooth= False # Surface Smoothing
+
+
+def load_lwo(filename,
+ context,
+ ADD_SUBD_MOD=True,
+ LOAD_HIDDEN=False,
+ SKEL_TO_ARM=True):
+ '''Read the LWO file, hand off to version specific function.'''
+ name, ext= os.path.splitext(os.path.basename(filename))
+ file= open(filename, 'rb')
+
+ try:
+ header, chunk_size, chunk_name = struct.unpack(">4s1L4s", file.read(12))
+ except:
+ print("Error parsing file header!")
+ file.close()
+ return
+
+ layers= []
+ surfs= {}
+ tags= []
+ # Gather the object data using the version specific handler.
+ if chunk_name == b'LWO2':
+ read_lwo2(file, filename, layers, surfs, tags, ADD_SUBD_MOD, LOAD_HIDDEN, SKEL_TO_ARM)
+ elif chunk_name == b'LWOB' or chunk_name == b'LWLO':
+ # LWOB and LWLO are the old format, LWLO is a layered object.
+ read_lwob(file, filename, layers, surfs, tags, ADD_SUBD_MOD)
+ else:
+ print("Not a supported file type!")
+ file.close()
+ return
+
+ file.close()
+
+ # With the data gathered, build the object(s).
+ build_objects(layers, surfs, tags, name, ADD_SUBD_MOD, SKEL_TO_ARM)
+
+ layers= None
+ surfs.clear()
+ tags= None
+
+
+def read_lwo2(file, filename, layers, surfs, tags, add_subd_mod, load_hidden, skel_to_arm):
+ '''Read version 2 file, LW 6+.'''
+ handle_layer= True
+ last_pols_count= 0
+ just_read_bones= False
+ print("Importing LWO: " + filename + "\nLWO v2 Format")
+
+ while True:
+ try:
+ rootchunk = chunk.Chunk(file)
+ except EOFError:
+ break
+
+ if rootchunk.chunkname == b'TAGS':
+ read_tags(rootchunk.read(), tags)
+ elif rootchunk.chunkname == b'LAYR':
+ handle_layer= read_layr(rootchunk.read(), layers, load_hidden)
+ elif rootchunk.chunkname == b'PNTS' and handle_layer:
+ read_pnts(rootchunk.read(), layers)
+ elif rootchunk.chunkname == b'VMAP' and handle_layer:
+ vmap_type = rootchunk.read(4)
+
+ if vmap_type == b'WGHT':
+ read_weightmap(rootchunk.read(), layers)
+ elif vmap_type == b'MORF':
+ read_morph(rootchunk.read(), layers, False)
+ elif vmap_type == b'SPOT':
+ read_morph(rootchunk.read(), layers, True)
+ elif vmap_type == b'TXUV':
+ read_uvmap(rootchunk.read(), layers)
+ elif vmap_type == b'RGB ' or vmap_type == b'RGBA':
+ read_colmap(rootchunk.read(), layers)
+ else:
+ rootchunk.skip()
+
+ elif rootchunk.chunkname == b'VMAD' and handle_layer:
+ vmad_type= rootchunk.read(4)
+
+ if vmad_type == b'TXUV':
+ read_uv_vmad(rootchunk.read(), layers, last_pols_count)
+ elif vmad_type == b'RGB ' or vmad_type == b'RGBA':
+ read_color_vmad(rootchunk.read(), layers, last_pols_count)
+ elif vmad_type == b'WGHT':
+ # We only read the Edge Weight map if it's there.
+ read_weight_vmad(rootchunk.read(), layers)
+ else:
+ rootchunk.skip()
+
+ elif rootchunk.chunkname == b'POLS' and handle_layer:
+ face_type = rootchunk.read(4)
+ just_read_bones= False
+ # PTCH is LW's Subpatches, SUBD is CatmullClark.
+ if (face_type == b'FACE' or face_type == b'PTCH' or
+ face_type == b'SUBD') and handle_layer:
+ last_pols_count= read_pols(rootchunk.read(), layers)
+ if face_type != b'FACE':
+ layers[-1].has_subds= True
+ elif face_type == b'BONE' and handle_layer:
+ read_bones(rootchunk.read(), layers)
+ just_read_bones= True
+ else:
+ rootchunk.skip()
+
+ elif rootchunk.chunkname == b'PTAG' and handle_layer:
+ tag_type,= struct.unpack("4s", rootchunk.read(4))
+ if tag_type == b'SURF' and not just_read_bones:
+ # Ignore the surface data if we just read a bones chunk.
+ read_surf_tags(rootchunk.read(), layers, last_pols_count)
+
+ elif skel_to_arm:
+ if tag_type == b'BNUP':
+ read_bone_tags(rootchunk.read(), layers, tags, 'BNUP')
+ elif tag_type == b'BONE':
+ read_bone_tags(rootchunk.read(), layers, tags, 'BONE')
+ else:
+ rootchunk.skip()
+ else:
+ rootchunk.skip()
+ elif rootchunk.chunkname == b'SURF':
+ read_surf(rootchunk.read(), surfs)
+ else:
+ #if handle_layer:
+ #print("Skipping Chunk:", rootchunk.chunkname)
+ rootchunk.skip()
+
+
+def read_lwob(file, filename, layers, surfs, tags, add_subd_mod):
+ '''Read version 1 file, LW < 6.'''
+ last_pols_count= 0
+ print("Importing LWO: " + filename + "\nLWO v1 Format")
+
+ while True:
+ try:
+ rootchunk = chunk.Chunk(file)
+ except EOFError:
+ break
+
+ if rootchunk.chunkname == b'SRFS':
+ read_tags(rootchunk.read(), tags)
+ elif rootchunk.chunkname == b'LAYR':
+ read_layr_5(rootchunk.read(), layers)
+ elif rootchunk.chunkname == b'PNTS':
+ if len(layers) == 0:
+ # LWOB files have no LAYR chunk to set this up.
+ nlayer= _obj_layer()
+ nlayer.name= "Layer 1"
+ layers.append(nlayer)
+ read_pnts(rootchunk.read(), layers)
+ elif rootchunk.chunkname == b'POLS':
+ last_pols_count= read_pols_5(rootchunk.read(), layers)
+ elif rootchunk.chunkname == b'PCHS':
+ last_pols_count= read_pols_5(rootchunk.read(), layers)
+ layers[-1].has_subds= True
+ elif rootchunk.chunkname == b'PTAG':
+ tag_type,= struct.unpack("4s", rootchunk.read(4))
+ if tag_type == b'SURF':
+ read_surf_tags_5(rootchunk.read(), layers, last_pols_count)
+ else:
+ rootchunk.skip()
+ elif rootchunk.chunkname == b'SURF':
+ read_surf_5(rootchunk.read(), surfs)
+ else:
+ # For Debugging \/.
+ #if handle_layer:
+ #print("Skipping Chunk: ", rootchunk.chunkname)
+ rootchunk.skip()
+
+
+def read_lwostring(raw_name):
+ '''Parse a zero-padded string.'''
+
+ i = raw_name.find(b'\0')
+ name_len = i + 1
+ if name_len % 2 == 1: # Test for oddness.
+ name_len += 1
+
+ if i > 0:
+ # Some plugins put non-text strings in the tags chunk.
+ name = raw_name[0:i].decode("utf-8", "ignore")
+ else:
+ name = ""
+
+ return name, name_len
+
+
+def read_vx(pointdata):
+ '''Read a variable-length index.'''
+ if pointdata[0] != 255:
+ index= pointdata[0]*256 + pointdata[1]
+ size= 2
+ else:
+ index= pointdata[1]*65536 + pointdata[2]*256 + pointdata[3]
+ size= 4
+
+ return index, size
+
+
+def read_tags(tag_bytes, object_tags):
+ '''Read the object's Tags chunk.'''
+ offset= 0
+ chunk_len= len(tag_bytes)
+
+ while offset < chunk_len:
+ tag, tag_len= read_lwostring(tag_bytes[offset:])
+ offset+= tag_len
+ object_tags.append(tag)
+
+
+def read_layr(layr_bytes, object_layers, load_hidden):
+ '''Read the object's layer data.'''
+ new_layr= _obj_layer()
+ new_layr.index, flags= struct.unpack(">HH", layr_bytes[0:4])
+
+ if flags > 0 and not load_hidden:
+ return False
+
+ print("Reading Object Layer")
+ offset= 4
+ pivot= struct.unpack(">fff", layr_bytes[offset:offset+12])
+ # Swap Y and Z to match Blender's pitch.
+ new_layr.pivot= [pivot[0], pivot[2], pivot[1]]
+ offset+= 12
+ layr_name, name_len = read_lwostring(layr_bytes[offset:])
+ offset+= name_len
+
+ if layr_name:
+ new_layr.name= layr_name
+ else:
+ new_layr.name= "Layer %d" % (new_layr.index + 1)
+
+ if len(layr_bytes) == offset+2:
+ new_layr.parent_index,= struct.unpack(">h", layr_bytes[offset:offset+2])
+
+ object_layers.append(new_layr)
+ return True
+
+
+def read_layr_5(layr_bytes, object_layers):
+ '''Read the object's layer data.'''
+ # XXX: Need to check what these two exactly mean for a LWOB/LWLO file.
+ new_layr= _obj_layer()
+ new_layr.index, flags= struct.unpack(">HH", layr_bytes[0:4])
+
+ print("Reading Object Layer")
+ offset= 4
+ layr_name, name_len = read_lwostring(layr_bytes[offset:])
+ offset+= name_len
+
+ if name_len > 2 and layr_name != 'noname':
+ new_layr.name= layr_name
+ else:
+ new_layr.name= "Layer %d" % new_layr.index
+
+ object_layers.append(new_layr)
+
+
+def read_pnts(pnt_bytes, object_layers):
+ '''Read the layer's points.'''
+ print("\tReading Layer ("+object_layers[-1].name+") Points")
+ offset= 0
+ chunk_len= len(pnt_bytes)
+
+ while offset < chunk_len:
+ pnts= struct.unpack(">fff", pnt_bytes[offset:offset+12])
+ offset+= 12
+ # Re-order the points so that the mesh has the right pitch,
+ # the pivot already has the correct order.
+ pnts= [pnts[0] - object_layers[-1].pivot[0],\
+ pnts[2] - object_layers[-1].pivot[1],\
+ pnts[1] - object_layers[-1].pivot[2]]
+ object_layers[-1].pnts.append(pnts)
+
+
+def read_weightmap(weight_bytes, object_layers):
+ '''Read a weight map's values.'''
+ chunk_len= len(weight_bytes)
+ offset= 2
+ name, name_len= read_lwostring(weight_bytes[offset:])
+ offset+= name_len
+ weights= []
+
+ while offset < chunk_len:
+ pnt_id, pnt_id_len= read_vx(weight_bytes[offset:offset+4])
+ offset+= pnt_id_len
+ value,= struct.unpack(">f", weight_bytes[offset:offset+4])
+ offset+= 4
+ weights.append([pnt_id, value])
+
+ object_layers[-1].wmaps[name]= weights
+
+
+def read_morph(morph_bytes, object_layers, is_abs):
+ '''Read an endomorph's relative or absolute displacement values.'''
+ chunk_len= len(morph_bytes)
+ offset= 2
+ name, name_len= read_lwostring(morph_bytes[offset:])
+ offset+= name_len
+ deltas= []
+
+ while offset < chunk_len:
+ pnt_id, pnt_id_len= read_vx(morph_bytes[offset:offset+4])
+ offset+= pnt_id_len
+ pos= struct.unpack(">fff", morph_bytes[offset:offset+12])
+ offset+= 12
+ pnt= object_layers[-1].pnts[pnt_id]
+
+ if is_abs:
+ deltas.append([pnt_id, pos[0], pos[2], pos[1]])
+ else:
+ # Swap the Y and Z to match Blender's pitch.
+ deltas.append([pnt_id, pnt[0]+pos[0], pnt[1]+pos[2], pnt[2]+pos[1]])
+
+ object_layers[-1].morphs[name]= deltas
+
+
+def read_colmap(col_bytes, object_layers):
+ '''Read the RGB or RGBA color map.'''
+ chunk_len= len(col_bytes)
+ dia,= struct.unpack(">H", col_bytes[0:2])
+ offset= 2
+ name, name_len= read_lwostring(col_bytes[offset:])
+ offset+= name_len
+ colors= {}
+
+ if dia == 3:
+ while offset < chunk_len:
+ pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
+ offset+= pnt_id_len
+ col= struct.unpack(">fff", col_bytes[offset:offset+12])
+ offset+= 12
+ colors[pnt_id]= (col[0], col[1], col[2])
+ elif dia == 4:
+ while offset < chunk_len:
+ pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
+ offset+= pnt_id_len
+ col= struct.unpack(">ffff", col_bytes[offset:offset+16])
+ offset+= 16
+ colors[pnt_id]= (col[0], col[1], col[2])
+
+ if name in object_layers[-1].colmaps:
+ if "PointMap" in object_layers[-1].colmaps[name]:
+ object_layers[-1].colmaps[name]["PointMap"].update(colors)
+ else:
+ object_layers[-1].colmaps[name]["PointMap"]= colors
+ else:
+ object_layers[-1].colmaps[name]= dict(PointMap=colors)
+
+
+def read_color_vmad(col_bytes, object_layers, last_pols_count):
+ '''Read the Discontinous (per-polygon) RGB values.'''
+ chunk_len= len(col_bytes)
+ dia,= struct.unpack(">H", col_bytes[0:2])
+ offset= 2
+ name, name_len= read_lwostring(col_bytes[offset:])
+ offset+= name_len
+ colors= {}
+ abs_pid= len(object_layers[-1].pols) - last_pols_count
+
+ if dia == 3:
+ while offset < chunk_len:
+ pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
+ offset+= pnt_id_len
+ pol_id, pol_id_len= read_vx(col_bytes[offset:offset+4])
+ offset+= pol_id_len
+
+ # The PolyID in a VMAD can be relative, this offsets it.
+ pol_id+= abs_pid
+ col= struct.unpack(">fff", col_bytes[offset:offset+12])
+ offset+= 12
+ if pol_id in colors:
+ colors[pol_id][pnt_id]= (col[0], col[1], col[2])
+ else:
+ colors[pol_id]= dict({pnt_id: (col[0], col[1], col[2])})
+ elif dia == 4:
+ while offset < chunk_len:
+ pnt_id, pnt_id_len= read_vx(col_bytes[offset:offset+4])
+ offset+= pnt_id_len
+ pol_id, pol_id_len= read_vx(col_bytes[offset:offset+4])
+ offset+= pol_id_len
+
+ pol_id+= abs_pid
+ col= struct.unpack(">ffff", col_bytes[offset:offset+16])
+ offset+= 16
+ if pol_id in colors:
+ colors[pol_id][pnt_id]= (col[0], col[1], col[2])
+ else:
+ colors[pol_id]= dict({pnt_id: (col[0], col[1], col[2])})
+
+ if name in object_layers[-1].colmaps:
+ if "FaceMap" in object_layers[-1].colmaps[name]:
+ object_layers[-1].colmaps[name]["FaceMap"].update(colors)
+ else:
+ object_layers[-1].colmaps[name]["FaceMap"]= colors
+ else:
+ object_layers[-1].colmaps[name]= dict(FaceMap=colors)
+
+
+def read_uvmap(uv_bytes, object_layers):
+ '''Read the simple UV coord values.'''
+ chunk_len= len(uv_bytes)
+ offset= 2
+ name, name_len= read_lwostring(uv_bytes[offset:])
+ offset+= name_len
+ uv_coords= {}
+
+ while offset < chunk_len:
+ pnt_id, pnt_id_len= read_vx(uv_bytes[offset:offset+4])
+ offset+= pnt_id_len
+ pos= struct.unpack(">ff", uv_bytes[offset:offset+8])
+ offset+= 8
+ uv_coords[pnt_id]= (pos[0], pos[1])
+
+ if name in object_layers[-1].uvmaps:
+ if "PointMap" in object_layers[-1].uvmaps[name]:
+ object_layers[-1].uvmaps[name]["PointMap"].update(uv_coords)
+ else:
+ object_layers[-1].uvmaps[name]["PointMap"]= uv_coords
+ else:
+ object_layers[-1].uvmaps[name]= dict(PointMap=uv_coords)
+
+
+def read_uv_vmad(uv_bytes, object_layers, last_pols_count):
+ '''Read the Discontinous (per-polygon) uv values.'''
+ chunk_len= len(uv_bytes)
+ offset= 2
+ name, name_len= read_lwostring(uv_bytes[offset:])
+ offset+= name_len
+ uv_coords= {}
+ abs_pid= len(object_layers[-1].pols) - last_pols_count
+
+ while offset < chunk_len:
+ pnt_id, pnt_id_len= read_vx(uv_bytes[offset:offset+4])
+ offset+= pnt_id_len
+ pol_id, pol_id_len= read_vx(uv_bytes[offset:offset+4])
+ offset+= pol_id_len
+
+ pol_id+= abs_pid
+ pos= struct.unpack(">ff", uv_bytes[offset:offset+8])
+ offset+= 8
+ if pol_id in uv_coords:
+ uv_coords[pol_id][pnt_id]= (pos[0], pos[1])
+ else:
+ uv_coords[pol_id]= dict({pnt_id: (pos[0], pos[1])})
+
+ if name in object_layers[-1].uvmaps:
+ if "FaceMap" in object_layers[-1].uvmaps[name]:
+ object_layers[-1].uvmaps[name]["FaceMap"].update(uv_coords)
+ else:
+ object_layers[-1].uvmaps[name]["FaceMap"]= uv_coords
+ else:
+ object_layers[-1].uvmaps[name]= dict(FaceMap=uv_coords)
+
+
+def read_weight_vmad(ew_bytes, object_layers):
+ '''Read the VMAD Weight values.'''
+ chunk_len= len(ew_bytes)
+ offset= 2
+ name, name_len= read_lwostring(ew_bytes[offset:])
+ if name != "Edge Weight":
+ return # We just want the Catmull-Clark edge weights
+
+ offset+= name_len
+ prev_pol= -1
+ prev_pnt= -1
+ prev_weight= 0.0
+ first_pnt= -1
+ poly_pnts= 0
+ while offset < chunk_len:
+ pnt_id, pnt_id_len= read_vx(ew_bytes[offset:offset+4])
+ offset+= pnt_id_len
+ pol_id, pol_id_len= read_vx(ew_bytes[offset:offset+4])
+ offset+= pol_id_len
+
+ weight,= struct.unpack(">f", ew_bytes[offset:offset+4])
+ offset+= 4
+ if prev_pol == pol_id:
+ # Points on the same poly should define an edge.
+ object_layers[-1].edge_weights["{0} {1}".format(prev_pnt, pnt_id)]= weight
+ poly_pnts += 1
+ else:
+ if poly_pnts > 2:
+ # Make an edge from the first and last points.
+ object_layers[-1].edge_weights["{0} {1}".format(first_pnt, prev_pnt)]= prev_weight
+ first_pnt= pnt_id
+ prev_pol= pol_id
+ poly_pnts= 1
+
+ prev_pnt= pnt_id
+ prev_weight= weight
+
+ if poly_pnts > 2:
+ object_layers[-1].edge_weights["{0} {1}".format(first_pnt, prev_pnt)]= prev_weight
+
+
+def read_pols(pol_bytes, object_layers):
+ '''Read the layer's polygons, each one is just a list of point indexes.'''
+ print("\tReading Layer ("+object_layers[-1].name+") Polygons")
+ offset= 0
+ pols_count = len(pol_bytes)
+ old_pols_count= len(object_layers[-1].pols)
+
+ while offset < pols_count:
+ pnts_count,= struct.unpack(">H", pol_bytes[offset:offset+2])
+ offset+= 2
+ all_face_pnts= []
+ for j in range(pnts_count):
+ face_pnt, data_size= read_vx(pol_bytes[offset:offset+4])
+ offset+= data_size
+ all_face_pnts.append(face_pnt)
+
+ object_layers[-1].pols.append(all_face_pnts)
+
+ return len(object_layers[-1].pols) - old_pols_count
+
+
+def read_pols_5(pol_bytes, object_layers):
+ '''
+ Read the polygons, each one is just a list of point indexes.
+ But it also includes the surface index.
+ '''
+ print("\tReading Layer ("+object_layers[-1].name+") Polygons")
+ offset= 0
+ chunk_len= len(pol_bytes)
+ old_pols_count= len(object_layers[-1].pols)
+ poly= 0
+
+ while offset < chunk_len:
+ pnts_count,= struct.unpack(">H", pol_bytes[offset:offset+2])
+ offset+= 2
+ all_face_pnts= []
+ for j in range(pnts_count):
+ face_pnt,= struct.unpack(">H", pol_bytes[offset:offset+2])
+ offset+= 2
+ all_face_pnts.append(face_pnt)
+
+ object_layers[-1].pols.append(all_face_pnts)
+ sid,= struct.unpack(">h", pol_bytes[offset:offset+2])
+ offset+= 2
+ sid= abs(sid) - 1
+ if sid not in object_layers[-1].surf_tags:
+ object_layers[-1].surf_tags[sid]= []
+ object_layers[-1].surf_tags[sid].append(poly)
+ poly+= 1
+
+ return len(object_layers[-1].pols) - old_pols_count
+
+
+def read_bones(bone_bytes, object_layers):
+ '''Read the layer's skelegons.'''
+ print("\tReading Layer ("+object_layers[-1].name+") Bones")
+ offset= 0
+ bones_count = len(bone_bytes)
+
+ while offset < bones_count:
+ pnts_count,= struct.unpack(">H", bone_bytes[offset:offset+2])
+ offset+= 2
+ all_bone_pnts= []
+ for j in range(pnts_count):
+ bone_pnt, data_size= read_vx(bone_bytes[offset:offset+4])
+ offset+= data_size
+ all_bone_pnts.append(bone_pnt)
+
+ object_layers[-1].bones.append(all_bone_pnts)
+
+
+def read_bone_tags(tag_bytes, object_layers, object_tags, type):
+ '''Read the bone name or roll tags.'''
+ offset= 0
+ chunk_len= len(tag_bytes)
+
+ if type == 'BONE':
+ bone_dict= object_layers[-1].bone_names
+ elif type == 'BNUP':
+ bone_dict= object_layers[-1].bone_rolls
+ else:
+ return
+
+ while offset < chunk_len:
+ pid, pid_len= read_vx(tag_bytes[offset:offset+4])
+ offset+= pid_len
+ tid,= struct.unpack(">H", tag_bytes[offset:offset+2])
+ offset+= 2
+ bone_dict[pid]= object_tags[tid]
+
+
+def read_surf_tags(tag_bytes, object_layers, last_pols_count):
+ '''Read the list of PolyIDs and tag indexes.'''
+ print("\tReading Layer ("+object_layers[-1].name+") Surface Assignments")
+ offset= 0
+ chunk_len= len(tag_bytes)
+
+ # Read in the PolyID/Surface Index pairs.
+ abs_pid= len(object_layers[-1].pols) - last_pols_count
+ while offset < chunk_len:
+ pid, pid_len= read_vx(tag_bytes[offset:offset+4])
+ offset+= pid_len
+ sid,= struct.unpack(">H", tag_bytes[offset:offset+2])
+ offset+=2
+ if sid not in object_layers[-1].surf_tags:
+ object_layers[-1].surf_tags[sid]= []
+ object_layers[-1].surf_tags[sid].append(pid + abs_pid)
+
+
+def read_surf(surf_bytes, object_surfs):
+ '''Read the object's surface data.'''
+ if len(object_surfs) == 0:
+ print("Reading Object Surfaces")
+
+ surf= _obj_surf()
+ name, name_len= read_lwostring(surf_bytes)
+ if len(name) != 0:
+ surf.name = name
+
+ # We have to read this, but we won't use it...yet.
+ s_name, s_name_len= read_lwostring(surf_bytes[name_len:])
+ offset= name_len+s_name_len
+ block_size= len(surf_bytes)
+ while offset < block_size:
+ subchunk_name,= struct.unpack("4s", surf_bytes[offset:offset+4])
+ offset+= 4
+ subchunk_len,= struct.unpack(">H", surf_bytes[offset:offset+2])
+ offset+= 2
+
+ # Now test which subchunk it is.
+ if subchunk_name == b'COLR':
+ surf.colr= struct.unpack(">fff", surf_bytes[offset:offset+12])
+ # Don't bother with any envelopes for now.
+
+ elif subchunk_name == b'DIFF':
+ surf.diff,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'LUMI':
+ surf.lumi,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'SPEC':
+ surf.spec,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'REFL':
+ surf.refl,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'RBLR':
+ surf.rblr,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'TRAN':
+ surf.tran,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'RIND':
+ surf.rind,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'TBLR':
+ surf.tblr,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'TRNL':
+ surf.trnl,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'GLOS':
+ surf.glos,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'SHRP':
+ surf.shrp,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'SMAN':
+ s_angle,= struct.unpack(">f", surf_bytes[offset:offset+4])
+ if s_angle > 0.0:
+ surf.smooth = True
+
+ offset+= subchunk_len
+
+ object_surfs[surf.name]= surf
+
+
+def read_surf_5(surf_bytes, object_surfs):
+ '''Read the object's surface data.'''
+ if len(object_surfs) == 0:
+ print("Reading Object Surfaces")
+
+ surf= _obj_surf()
+ name, name_len= read_lwostring(surf_bytes)
+ if len(name) != 0:
+ surf.name = name
+
+ offset= name_len
+ chunk_len= len(surf_bytes)
+ while offset < chunk_len:
+ subchunk_name,= struct.unpack("4s", surf_bytes[offset:offset+4])
+ offset+= 4
+ subchunk_len,= struct.unpack(">H", surf_bytes[offset:offset+2])
+ offset+= 2
+
+ # Now test which subchunk it is.
+ if subchunk_name == b'COLR':
+ color= struct.unpack(">BBBB", surf_bytes[offset:offset+4])
+ surf.colr= [color[0] / 255.0, color[1] / 255.0, color[2] / 255.0]
+
+ elif subchunk_name == b'DIFF':
+ surf.diff,= struct.unpack(">h", surf_bytes[offset:offset+2])
+ surf.diff/= 256.0 # Yes, 256 not 255.
+
+ elif subchunk_name == b'LUMI':
+ surf.lumi,= struct.unpack(">h", surf_bytes[offset:offset+2])
+ surf.lumi/= 256.0
+
+ elif subchunk_name == b'SPEC':
+ surf.spec,= struct.unpack(">h", surf_bytes[offset:offset+2])
+ surf.spec/= 256.0
+
+ elif subchunk_name == b'REFL':
+ surf.refl,= struct.unpack(">h", surf_bytes[offset:offset+2])
+ surf.refl/= 256.0
+
+ elif subchunk_name == b'TRAN':
+ surf.tran,= struct.unpack(">h", surf_bytes[offset:offset+2])
+ surf.tran/= 256.0
+
+ elif subchunk_name == b'RIND':
+ surf.rind,= struct.unpack(">f", surf_bytes[offset:offset+4])
+
+ elif subchunk_name == b'GLOS':
+ surf.glos,= struct.unpack(">h", surf_bytes[offset:offset+2])
+
+ elif subchunk_name == b'SMAN':
+ s_angle,= struct.unpack(">f", surf_bytes[offset:offset+4])
+ if s_angle > 0.0:
+ surf.smooth = True
+
+ offset+= subchunk_len
+
+ object_surfs[surf.name]= surf
+
+
+def create_mappack(data, map_name, map_type):
+ '''Match the map data to faces.'''
+ pack= {}
+
+ def color_pointmap(map):
+ for fi in range(len(data.pols)):
+ if fi not in pack:
+ pack[fi]= []
+ for pnt in data.pols[fi]:
+ if pnt in map:
+ pack[fi].append(map[pnt])
+ else:
+ pack[fi].append((1.0, 1.0, 1.0))
+
+ def color_facemap(map):
+ for fi in range(len(data.pols)):
+ if fi not in pack:
+ pack[fi]= []
+ for p in data.pols[fi]:
+ pack[fi].append((1.0, 1.0, 1.0))
+ if fi in map:
+ for po in range(len(data.pols[fi])):
+ if data.pols[fi][po] in map[fi]:
+ pack[fi].insert(po, map[fi][data.pols[fi][po]])
+ del pack[fi][po+1]
+
+ def uv_pointmap(map):
+ for fi in range(len(data.pols)):
+ if fi not in pack:
+ pack[fi]= []
+ for p in data.pols[fi]:
+ pack[fi].append((-0.1,-0.1))
+ for po in range(len(data.pols[fi])):
+ pnt_id= data.pols[fi][po]
+ if pnt_id in map:
+ pack[fi].insert(po, map[pnt_id])
+ del pack[fi][po+1]
+
+ def uv_facemap(map):
+ for fi in range(len(data.pols)):
+ if fi not in pack:
+ pack[fi]= []
+ for p in data.pols[fi]:
+ pack[fi].append((-0.1,-0.1))
+ if fi in map:
+ for po in range(len(data.pols[fi])):
+ pnt_id= data.pols[fi][po]
+ if pnt_id in map[fi]:
+ pack[fi].insert(po, map[fi][pnt_id])
+ del pack[fi][po+1]
+
+ if map_type == "COLOR":
+ # Look at the first map, is it a point or face map
+ if "PointMap" in data.colmaps[map_name]:
+ color_pointmap(data.colmaps[map_name]["PointMap"])
+
+ if "FaceMap" in data.colmaps[map_name]:
+ color_facemap(data.colmaps[map_name]["FaceMap"])
+ elif map_type == "UV":
+ if "PointMap" in data.uvmaps[map_name]:
+ uv_pointmap(data.uvmaps[map_name]["PointMap"])
+
+ if "FaceMap" in data.uvmaps[map_name]:
+ uv_facemap(data.uvmaps[map_name]["FaceMap"])
+
+ return pack
+
+
+def build_armature(layer_data, bones):
+ '''Build an armature from the skelegon data in the mesh.'''
+ print("Building Armature")
+
+ # New Armatures include a default bone, remove it.
+ bones.remove(bones[0])
+
+ # Now start adding the bones at the point locations.
+ prev_bone= None
+ for skb_idx in range(len(layer_data.bones)):
+ if skb_idx in layer_data.bone_names:
+ nb= bones.new(layer_data.bone_names[skb_idx])
+ else:
+ nb= bones.new("Bone")
+
+ nb.head= layer_data.pnts[layer_data.bones[skb_idx][0]]
+ nb.tail= layer_data.pnts[layer_data.bones[skb_idx][1]]
+
+ if skb_idx in layer_data.bone_rolls:
+ xyz= layer_data.bone_rolls[skb_idx].split(' ')
+ vec= mathutils.Vector((float(xyz[0]), float(xyz[1]), float(xyz[2])))
+ quat= vec.to_track_quat('Y', 'Z')
+ nb.roll= max(quat.to_euler('YZX'))
+ if nb.roll == 0.0:
+ nb.roll= min(quat.to_euler('YZX')) * -1
+ # YZX order seems to produce the correct roll value.
+ else:
+ nb.roll= 0.0
+
+ if prev_bone != None:
+ if nb.head == prev_bone.tail:
+ nb.parent= prev_bone
+
+ nb.use_connect= True
+ prev_bone= nb
+
+
+def build_objects(object_layers, object_surfs, object_tags, object_name, add_subd_mod, skel_to_arm):
+ '''Using the gathered data, create the objects.'''
+ ob_dict= {} # Used for the parenting setup.
+ print("Adding %d Materials" % len(object_surfs))
+
+ for surf_key in object_surfs:
+ surf_data= object_surfs[surf_key]
+ surf_data.bl_mat= bpy.data.materials.new(surf_data.name)
+ surf_data.bl_mat.diffuse_color= (surf_data.colr[:])
+ surf_data.bl_mat.diffuse_intensity= surf_data.diff
+ surf_data.bl_mat.emit= surf_data.lumi
+ surf_data.bl_mat.specular_intensity= surf_data.spec
+ if surf_data.refl != 0.0:
+ surf_data.bl_mat.raytrace_mirror.use= True
+ surf_data.bl_mat.raytrace_mirror.reflect_factor= surf_data.refl
+ surf_data.bl_mat.raytrace_mirror.gloss_factor= 1.0-surf_data.rblr
+ if surf_data.tran != 0.0:
+ surf_data.bl_mat.use_transparency= True
+ surf_data.bl_mat.transparency_method= 'RAYTRACE'
+ surf_data.bl_mat.alpha= 1.0 - surf_data.tran
+ surf_data.bl_mat.raytrace_transparency.ior= surf_data.rind
+ surf_data.bl_mat.raytrace_transparency.gloss_factor= 1.0 - surf_data.tblr
+ surf_data.bl_mat.translucency= surf_data.trnl
+ surf_data.bl_mat.specular_hardness= int(4*((10*surf_data.glos)*(10*surf_data.glos)))+4
+ # The Gloss is as close as possible given the differences.
+
+ # Single layer objects use the object file's name instead.
+ if len(object_layers) and object_layers[-1].name == 'Layer 1':
+ object_layers[-1].name= object_name
+ print("Building '%s' Object" % object_name)
+ else:
+ print("Building %d Objects" % len(object_layers))
+
+ # Before adding any meshes or armatures go into Object mode.
+ if bpy.ops.object.mode_set.poll():
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ for layer_data in object_layers:
+ me= bpy.data.meshes.new(layer_data.name)
+ me.vertices.add(len(layer_data.pnts))
+ me.faces.add(len(layer_data.pols))
+ # for vi in range(len(layer_data.pnts)):
+ # me.vertices[vi].co= layer_data.pnts[vi]
+
+ # faster, would be faster again to use an array
+ me.vertices.foreach_set("co", [axis for co in layer_data.pnts for axis in co])
+
+ ngons= {} # To keep the FaceIdx consistant, handle NGons later.
+ edges= [] # Holds the FaceIdx of the 2-point polys.
+ for fi, fpol in enumerate(layer_data.pols):
+ fpol.reverse() # Reversing gives correct normal directions
+ # PointID 0 in the last element causes Blender to think it's un-used.
+ if fpol[-1] == 0:
+ fpol.insert(0, fpol[-1])
+ del fpol[-1]
+
+ vlen= len(fpol)
+ if vlen == 3 or vlen == 4:
+ for i in range(vlen):
+ me.faces[fi].vertices_raw[i]= fpol[i]
+ elif vlen == 2:
+ edges.append(fi)
+ elif vlen != 1:
+ ngons[fi]= fpol # Deal with them later
+
+ ob= bpy.data.objects.new(layer_data.name, me)
+ bpy.context.scene.objects.link(ob)
+ ob_dict[layer_data.index]= [ob, layer_data.parent_index]
+
+ # Move the object so the pivot is in the right place.
+ ob.location= layer_data.pivot
+
+ # Create the Material Slots and assign the MatIndex to the correct faces.
+ mat_slot= 0
+ for surf_key in layer_data.surf_tags:
+ if object_tags[surf_key] in object_surfs:
+ me.materials.append(object_surfs[object_tags[surf_key]].bl_mat)
+
+ for fi in layer_data.surf_tags[surf_key]:
+ me.faces[fi].material_index= mat_slot
+ me.faces[fi].use_smooth= object_surfs[object_tags[surf_key]].smooth
+
+ mat_slot+=1
+
+ # Create the Vertex Groups (LW's Weight Maps).
+ if len(layer_data.wmaps) > 0:
+ print("Adding %d Vertex Groups" % len(layer_data.wmaps))
+ for wmap_key in layer_data.wmaps:
+ vgroup= ob.vertex_groups.new()
+ vgroup.name= wmap_key
+ wlist= layer_data.wmaps[wmap_key]
+ for pvp in wlist:
+ ob.vertex_groups.assign([pvp[0]], vgroup, pvp[1], 'REPLACE')
+
+ # Create the Shape Keys (LW's Endomorphs).
+ if len(layer_data.morphs) > 0:
+ print("Adding %d Shapes Keys" % len(layer_data.morphs))
+ ob.shape_key_add('Basis') # Got to have a Base Shape.
+ for morph_key in layer_data.morphs:
+ skey= ob.shape_key_add(morph_key)
+ dlist= layer_data.morphs[morph_key]
+ for pdp in dlist:
+ me.shape_keys.keys[skey.name].data[pdp[0]].co= [pdp[1], pdp[2], pdp[3]]
+
+ # Create the Vertex Color maps.
+ if len(layer_data.colmaps) > 0:
+ print("Adding %d Vertex Color Maps" % len(layer_data.colmaps))
+ for cmap_key in layer_data.colmaps:
+ map_pack= create_mappack(layer_data, cmap_key, "COLOR")
+ vcol= me.vertex_colors.new(cmap_key)
+ if not vcol:
+ break
+ for fi in map_pack:
+ if fi > len(vcol.data):
+ continue
+ face= map_pack[fi]
+ colf= vcol.data[fi]
+
+ if len(face) > 2:
+ colf.color1= face[0]
+ colf.color2= face[1]
+ colf.color3= face[2]
+ if len(face) == 4:
+ colf.color4= face[3]
+
+ # Create the UV Maps.
+ if len(layer_data.uvmaps) > 0:
+ print("Adding %d UV Textures" % len(layer_data.uvmaps))
+ for uvmap_key in layer_data.uvmaps:
+ map_pack= create_mappack(layer_data, uvmap_key, "UV")
+ uvm= me.uv_textures.new(uvmap_key)
+ if not uvm:
+ break
+ for fi in map_pack:
+ if fi > len(uvm.data):
+ continue
+ face= map_pack[fi]
+ uvf= uvm.data[fi]
+
+ if len(face) > 2:
+ uvf.uv1= face[0]
+ uvf.uv2= face[1]
+ uvf.uv3= face[2]
+ if len(face) == 4:
+ uvf.uv4= face[3]
+
+ # Now add the NGons.
+ if len(ngons) > 0:
+ for ng_key in ngons:
+ face_offset= len(me.faces)
+ ng= ngons[ng_key]
+ v_locs= []
+ for vi in range(len(ng)):
+ v_locs.append(mathutils.Vector(layer_data.pnts[ngons[ng_key][vi]]))
+ tris= tesselate_polygon([v_locs])
+ me.faces.add(len(tris))
+ for tri in tris:
+ face= me.faces[face_offset]
+ face.vertices_raw[0]= ng[tri[0]]
+ face.vertices_raw[1]= ng[tri[1]]
+ face.vertices_raw[2]= ng[tri[2]]
+ face.material_index= me.faces[ng_key].material_index
+ face.use_smooth= me.faces[ng_key].use_smooth
+ face_offset+= 1
+
+ # FaceIDs are no longer a concern, so now update the mesh.
+ has_edges= len(edges) > 0 or len(layer_data.edge_weights) > 0
+ me.update(calc_edges=has_edges)
+
+ # Add the edges.
+ edge_offset= len(me.edges)
+ me.edges.add(len(edges))
+ for edge_fi in edges:
+ me.edges[edge_offset].vertices[0]= layer_data.pols[edge_fi][0]
+ me.edges[edge_offset].vertices[1]= layer_data.pols[edge_fi][1]
+ edge_offset+= 1
+
+ # Apply the Edge Weighting.
+ if len(layer_data.edge_weights) > 0:
+ for edge in me.edges:
+ edge_sa= "{0} {1}".format(edge.vertices[0], edge.vertices[1])
+ edge_sb= "{0} {1}".format(edge.vertices[1], edge.vertices[0])
+ if edge_sa in layer_data.edge_weights:
+ edge.crease= layer_data.edge_weights[edge_sa]
+ elif edge_sb in layer_data.edge_weights:
+ edge.crease= layer_data.edge_weights[edge_sb]
+
+ # Unfortunately we can't exlude certain faces from the subdivision.
+ if layer_data.has_subds and add_subd_mod:
+ ob.modifiers.new(name="Subsurf", type='SUBSURF')
+
+ # Should we build an armature from the embedded rig?
+ if len(layer_data.bones) > 0 and skel_to_arm:
+ bpy.ops.object.armature_add()
+ arm_object= bpy.context.active_object
+ arm_object.name= "ARM_" + layer_data.name
+ arm_object.data.name= arm_object.name
+ arm_object.location= layer_data.pivot
+ bpy.ops.object.mode_set(mode='EDIT')
+ build_armature(layer_data, arm_object.data.edit_bones)
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Clear out the dictionaries for this layer.
+ layer_data.bone_names.clear()
+ layer_data.bone_rolls.clear()
+ layer_data.wmaps.clear()
+ layer_data.colmaps.clear()
+ layer_data.uvmaps.clear()
+ layer_data.morphs.clear()
+ layer_data.surf_tags.clear()
+
+ # With the objects made, setup the parents and re-adjust the locations.
+ for ob_key in ob_dict:
+ if ob_dict[ob_key][1] != -1 and ob_dict[ob_key][1] in ob_dict:
+ parent_ob = ob_dict[ob_dict[ob_key][1]]
+ ob_dict[ob_key][0].parent= parent_ob[0]
+ ob_dict[ob_key][0].location-= parent_ob[0].location
+
+ bpy.context.scene.update()
+
+ print("Done Importing LWO File")
+
+
+from bpy.props import *
+
+
+class IMPORT_OT_lwo(bpy.types.Operator):
+ '''Import LWO Operator.'''
+ bl_idname= "import_scene.lwo"
+ bl_label= "Import LWO"
+ bl_description= "Import a LightWave Object file."
+ bl_options= {'REGISTER', 'UNDO'}
+
+ filepath= StringProperty(name="File Path", description="Filepath used for importing the LWO file", maxlen=1024, default="")
+
+ ADD_SUBD_MOD= BoolProperty(name="Apply SubD Modifier", description="Apply the Subdivision Surface modifier to layers with Subpatches", default=True)
+ LOAD_HIDDEN= BoolProperty(name="Load Hidden Layers", description="Load object layers that have been marked as hidden", default=False)
+ SKEL_TO_ARM= BoolProperty(name="Create Armature", description="Create an armature from an embedded Skelegon rig", default=True)
+
+ def execute(self, context):
+ load_lwo(self.filepath,
+ context,
+ self.ADD_SUBD_MOD,
+ self.LOAD_HIDDEN,
+ self.SKEL_TO_ARM)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm= context.window_manager
+ wm.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+
+def menu_func(self, context):
+ self.layout.operator(IMPORT_OT_lwo.bl_idname, text="LightWave Object (.lwo)")
+
+
+def register():
+ bpy.types.INFO_MT_file_import.append(menu_func)
+
+
+def unregister():
+ 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..76d965a4
--- /dev/null
+++ b/io_import_scene_mhx.py
@@ -0,0 +1,2480 @@
+"""
+**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 1.0.3
+
+"""
+
+bl_addon_info = {
+ 'name': 'Import: MakeHuman (.mhx)',
+ 'author': 'Thomas Larsson',
+ 'version': (1, 0, 3),
+ 'blender': (2, 5, 5),
+ 'api': 33590,
+ 'location': "File > Import",
+ 'description': 'Import files in the MakeHuman eXchange format (.mhx)',
+ 'warning': '',
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/'\
+ 'Scripts/Import-Export/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.
+"""
+
+MAJOR_VERSION = 1
+MINOR_VERSION = 0
+SUB_VERSION = 3
+BLENDER_VERSION = (2, 55, 1)
+
+#
+#
+#
+
+import bpy
+import os
+import time
+import mathutils
+from mathutils import *
+#import geometry
+#import string
+
+MHX249 = False
+Blender24 = False
+Blender25 = True
+TexDir = "~/makehuman/exports"
+
+#
+#
+#
+
+theScale = 1.0
+One = 1.0/theScale
+useMesh = 1
+verbosity = 2
+warnedTextureDir = False
+warnedVersion = False
+
+true = True
+false = False
+Epsilon = 1e-6
+nErrors = 0
+theTempDatum = None
+
+todo = []
+
+#
+# toggle flags
+#
+
+T_EnforceVersion = 0x01
+T_Clothes = 0x02
+T_Stretch = 0x04
+T_Bend = 0x08
+
+T_Diamond = 0x10
+T_Replace = 0x20
+T_Face = 0x40
+T_Shape = 0x80
+
+T_Mesh = 0x100
+T_Armature = 0x200
+T_Proxy = 0x400
+T_Cage = 0x800
+
+T_Rigify = 0x1000
+T_Preset = 0x2000
+T_Symm = 0x4000
+T_MHX = 0x8000
+
+toggle = T_EnforceVersion + T_Replace + T_Mesh + T_Armature + T_Face + T_Shape + T_Proxy + T_Clothes
+
+#
+# setFlagsAndFloats(rigFlags):
+#
+# Global floats
+#fFingerPanel = 0.0
+#fFingerIK = 0.0
+fNoStretch = 0.0
+
+# rigLeg and rigArm flags
+T_Toes = 0x0001
+#T_GoboFoot = 0x0002
+
+#T_InvFoot = 0x0010
+#T_InvFootPT = 0x0020
+#T_InvFootNoPT = 0x0040
+
+#T_FingerPanel = 0x100
+#T_FingerRot = 0x0200
+#T_FingerIK = 0x0400
+
+
+#T_LocalFKIK = 0x8000
+
+#rigLeg = 0
+#rigArm = 0
+
+def setFlagsAndFloats():
+ '''
+ global toggle, rigLeg, rigArm
+
+ (footRig, fingerRig) = rigFlags
+ rigLeg = 0
+ if footRig == 'Reverse foot':
+ rigLeg |= T_InvFoot
+ if toggle & T_PoleTar:
+ rigLeg |= T_InvFootPT
+ else:
+ rigLeg |= T_InvFootNoPT
+ elif footRig == 'Gobo': rigLeg |= T_GoboFoot
+
+ rigArm = 0
+ if fingerRig == 'Panel': rigArm |= T_FingerPanel
+ elif fingerRig == 'Rotation': rigArm |= T_FingerRot
+ elif fingerRig == 'IK': rigArm |= T_FingerIK
+
+ toggle |= T_Panel
+ '''
+ global fNoStretch
+ if toggle&T_Stretch: fNoStretch == 0.0
+ else: fNoStretch = 1.0
+
+ return
+
+
+#
+# Dictionaries
+#
+
+loadedData = {
+ 'NONE' : {},
+
+ 'Object' : {},
+ 'Mesh' : {},
+ 'Armature' : {},
+ 'Lamp' : {},
+ 'Camera' : {},
+ 'Lattice' : {},
+ 'Curve' : {},
+ 'Text' : {},
+
+ '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',
+ 'Text' : 'texts',
+ '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',
+}
+
+#
+# checkBlenderVersion()
+#
+
+def checkBlenderVersion():
+ print("Found Blender", bpy.app.version)
+ (A, B, C) = bpy.app.version
+ (a, b, c) = BLENDER_VERSION
+ if a <= A: return
+ if b <= B: return
+ if c <= C: return
+ msg = (
+"This version of the MHX importer only works with Blender (%d, %d, %d) or later. " % (a, b, c) +
+"Download a more recent Blender from www.blender.org or www.graphicall.org.\n"
+ )
+ raise NameError(msg)
+ return
+
+#
+# readMhxFile(filePath, scale):
+#
+
+def readMhxFile(filePath, scale):
+ global todo, nErrors, theScale, defaultScale, One, toggle
+
+ checkBlenderVersion()
+
+ theScale = scale
+ defaultScale = scale
+ One = 1.0/theScale
+
+ fileName = os.path.expanduser(filePath)
+ (shortName, ext) = os.path.splitext(fileName)
+ if ext.lower() != ".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
+ comment = 0
+ nesting = 0
+
+ setFlagsAndFloats()
+
+ 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][0] == '#':
+ if lineSplit[0] == '#if':
+ if comment == nesting:
+ try:
+ res = eval(lineSplit[1])
+ except:
+ res = False
+ if res:
+ comment += 1
+ nesting += 1
+ elif lineSplit[0] == '#else':
+ if comment == nesting-1:
+ comment += 1
+ elif comment == nesting:
+ comment -= 1
+ elif lineSplit[0] == '#endif':
+ if comment == nesting:
+ comment -= 1
+ nesting -= 1
+ elif comment < nesting:
+ 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
+
+#
+# getObject(name, var, glbals, lcals):
+#
+
+def getObject(name, var, glbals, lcals):
+ try:
+ ob = loadedData['Object'][name]
+ except:
+ if name != "None":
+ pushOnTodoList(None, "ob = loadedData['Object'][name]" % globals(), locals())
+ ob = None
+ return ob
+
+#
+# checkMhxVersion(major, minor):
+#
+
+def checkMhxVersion(major, minor):
+ global warnedVersion
+ if major != MAJOR_VERSION or minor != MINOR_VERSION:
+ if warnedVersion:
+ return
+ else:
+ msg = (
+"Wrong MHX version\n" +
+"Expected MHX %d.%d but the loaded file has version MHX %d.%d\n" % (MAJOR_VERSION, MINOR_VERSION, major, minor) +
+"You can disable this error message by deselecting the Enforce version option when importing. " +
+"Alternatively, you can try to download the most recent nightly build from www.makehuman.org. " +
+"The current version of the import script is located in the importers/mhx/blender25x folder and is called import_scene_mhx.py. " +
+"The version distributed with Blender builds from www.graphicall.org may be out of date.\n"
+)
+ if toggle & T_EnforceVersion:
+ raise NameError(msg)
+ else:
+ print(msg)
+ warnedVersion = True
+ return
+
+#
+# parse(tokens):
+#
+
+ifResult = False
+
+def parse(tokens):
+ global MHX249, ifResult, theScale, defaultScale, One
+
+ for (key, val, sub) in tokens:
+ # print("Parse %s" % key)
+ data = None
+ if key == 'MHX':
+ checkMhxVersion(int(val[0]), int(val[1]))
+ elif key == 'MHX249':
+ MHX249 = eval(val[0])
+ print("Blender 2.49 compatibility mode is %s\n" % MHX249)
+ 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 == 'NoScale':
+ if eval(val[0]):
+ theScale = 1.0
+ else:
+ theScale = defaultScale
+ One = 1.0/theScale
+ elif key == "Object":
+ parseObject(val, sub)
+ elif key == "Mesh":
+ data = parseMesh(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 == "Curve":
+ data = parseCurve(val, sub)
+ elif key == "TextCurve":
+ data = parseTextCurve(val, sub)
+ elif key == "Lattice":
+ data = parseLattice(val, sub)
+ elif key == "Group":
+ data = parseGroup(val, sub)
+ elif key == "Lamp":
+ data = parseLamp(val, sub)
+ elif key == "World":
+ data = parseWorld(val, sub)
+ elif key == "Scene":
+ data = parseScene(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, val, sub)
+ elif key == 'MaterialAnimationData':
+ try:
+ ob = loadedData['Object'][val[0]]
+ except:
+ ob = None
+ if ob:
+ bpy.context.scene.objects.active = ob
+ mat = ob.data.materials[int(val[2])]
+ print("matanim", ob, mat)
+ parseAnimationData(mat, val, sub)
+ elif key == 'ShapeKeys':
+ try:
+ ob = loadedData['Object'][val[0]]
+ except:
+ raise NameError("ShapeKeys object %s does not exist" % val[0])
+ 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, args, tokens):
+# parseDriver(drv, args, tokens):
+# parseDriverVariable(var, args, tokens):
+#
+
+def parseAnimationData(rna, args, tokens):
+ if not eval(args[1]):
+ 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)
+ n = 1
+ for (key, val, sub) in tokens:
+ if key == 'Driver':
+ fcu = parseDriver(adata, dataPath, index, rna, val, sub)
+ fmod = fcu.modifiers[0]
+ fcu.modifiers.remove(fmod)
+ elif key == 'FModifier':
+ parseFModifier(fcu, val, sub)
+ elif key == 'kp':
+ pt = fcu.keyframe_points.add(n, 0)
+ pt.interpolation = 'LINEAR'
+ pt = parseKeyFramePoint(pt, val, sub)
+ n += 1
+ 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(args[0])
+ #fmod = fcu.modifiers[0]
+ 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.id = loadedData['Object'][args[0]]
+ for (key, val, sub) in tokens:
+ defaultKey(key, val, sub, 'targ', [], globals(), locals())
+ return targ
+
+
+#
+# parseMaterial(args, ext, tokens):
+# parseMTex(mat, args, tokens):
+# parseTexture(args, tokens):
+#
+
+def parseMaterial(args, tokens):
+ global todo
+ name = args[0]
+ mat = bpy.data.materials.new(name)
+ if mat == None:
+ return None
+ loadedData['Material'][name] = mat
+ for (key, val, sub) in tokens:
+ if key == 'MTex':
+ parseMTex(mat, val, sub)
+ elif key == 'Ramp':
+ parseRamp(mat, val, sub)
+ elif key == 'RaytraceTransparency':
+ parseDefault(mat.raytrace_transparency, sub, {}, [])
+ elif key == 'Halo':
+ parseDefault(mat.halo, sub, {}, [])
+ elif key == 'SSS':
+ parseDefault(mat.subsurface_scattering, sub, {}, [])
+ elif key == 'Strand':
+ parseDefault(mat.strand, sub, {}, [])
+ elif key == 'NodeTree':
+ mat.use_nodes = True
+ parseNodeTree(mat.node_tree, val, sub)
+ else:
+ exclude = ['specular_intensity', 'tangent_shading']
+ defaultKey(key, val, sub, 'mat', [], globals(), locals())
+
+ return mat
+
+def parseMTex(mat, args, tokens):
+ global todo
+ index = int(args[0])
+ texname = args[1]
+ texco = args[2]
+ mapto = args[3]
+ tex = loadedData['Texture'][texname]
+ mtex = mat.texture_slots.add()
+ mtex.texture_coords = texco
+ mtex.texture = tex
+
+ 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=name, type=args[1])
+ 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)
+ elif key == 'NodeTree':
+ tex.use_nodes = True
+ parseNodeTree(tex.node_tree, 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())
+
+#
+# parseNodeTree(tree, args, tokens):
+# parseNode(node, args, tokens):
+# parseSocket(socket, args, tokens):
+#
+
+def parseNodeTree(tree, args, tokens):
+ return
+ print("Tree", tree, args)
+ print(list(tree.nodes))
+ tree.name = args[0]
+ for (key, val, sub) in tokens:
+ if key == 'Node':
+ parseNodes(tree.nodes, val, sub)
+ else:
+ defaultKey(key, val, sub, "tree", [], globals(), locals())
+
+def parseNodes(nodes, args, tokens):
+ print("Nodes", nodes, args)
+ print(list(nodes))
+ node.name = args[0]
+ for (key, val, sub) in tokens:
+ if key == 'Inputs':
+ parseSocket(node.inputs, val, sub)
+ elif key == 'Outputs':
+ parseSocket(node.outputs, val, sub)
+ else:
+ defaultKey(key, val, sub, "node", [], globals(), locals())
+
+def parseNode(node, args, tokens):
+ print("Node", node, args)
+ print(list(node.inputs), list(node.outputs))
+ node.name = args[0]
+ for (key, val, sub) in tokens:
+ if key == 'Inputs':
+ parseSocket(node.inputs, val, sub)
+ elif key == 'Outputs':
+ parseSocket(node.outputs, val, sub)
+ else:
+ defaultKey(key, val, sub, "node", [], globals(), locals())
+
+def parseSocket(socket, args, tokens):
+ print("Socket", socket, args)
+ socket.name = args[0]
+ for (key, val, sub) in tokens:
+ if key == 'Node':
+ parseNode(tree.nodes, val, sub)
+ else:
+ defaultKey(key, val, sub, "tree", [], 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):
+# setObjectAndData(args, typ):
+#
+
+def parseObject(args, tokens):
+ if verbosity > 2:
+ print( "Parsing object %s" % args )
+ name = args[0]
+ typ = args[1]
+ datName = args[2]
+
+ if typ == 'EMPTY':
+ ob = bpy.data.objects.new(name, None)
+ loadedData['Object'][name] = ob
+ linkObject(ob, None)
+ else:
+ try:
+ data = loadedData[typ.capitalize()][datName]
+ except:
+ raise NameError("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", ob)
+ except:
+ ob = None
+
+ if ob == None:
+ print("Create", name, data, datName)
+ ob = createObject(typ, name, data, datName)
+ print("created", ob)
+ linkObject(ob, data)
+
+ for (key, val, sub) in tokens:
+ if key == 'Modifier':
+ parseModifier(ob, val, sub)
+ elif key == 'Constraint':
+ parseConstraint(ob.constraints, val, sub)
+ elif key == 'AnimationData':
+ parseAnimationData(ob, val, sub)
+ elif key == 'ParticleSystem':
+ parseParticleSystem(ob, val, sub)
+ elif key == 'FieldSettings':
+ parseDefault(ob.field, sub, {}, [])
+ else:
+ defaultKey(key, val, sub, "ob", ['type', 'data'], globals(), locals())
+
+ # Needed for updating layers
+ if bpy.context.object == ob:
+ pass
+ '''
+ if ob.data in ['MESH', 'ARMATURE']:
+ print(ob, ob.data)
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ '''
+ else:
+ print("Context", ob, bpy.context.object, bpy.context.scene.objects.active)
+ return
+
+def createObject(typ, name, data, datName):
+ # print( "Creating object %s %s %s" % (typ, name, data) )
+ ob = bpy.data.objects.new(name, data)
+ if data:
+ loadedData[typ.capitalize()][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
+ print("Data linked", ob, ob.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 setObjectAndData(args, typ):
+ datName = args[0]
+ obName = args[1]
+ #bpy.ops.object.add(type=typ)
+ 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:
+ me.from_pydata(verts, [], faces)
+ else:
+ me.from_pydata(verts, edges, [])
+ me.update()
+ linkObject(ob, me)
+
+ mats = []
+ for (key, val, sub) in tokens:
+ if key == 'Verts' or key == 'Edges' or key == 'Faces':
+ pass
+ 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:
+ mat = loadedData['Material'][val[0]]
+ except:
+ mat = None
+ if mat:
+ me.materials.append(mat)
+ else:
+ defaultKey(key, val, sub, "me", [], globals(), locals())
+
+ for (key, val, sub) in tokens:
+ if key == 'Faces':
+ parseFaces2(sub, me)
+
+ for n,mat in enumerate(list(me.materials)):
+ print(n, mat)
+ 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( (theScale*float(val[0]), theScale*float(val[1]), theScale*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])]
+ 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.use_smooth = int(val[1])
+ n += 1
+ elif key == 'mn':
+ fn = int(val[0])
+ mn = int(val[1])
+ f = me.faces[fn]
+ f.material_index = mn
+ elif key == 'ftall':
+ mat = int(val[0])
+ smooth = int(val[1])
+ for f in me.faces:
+ f.material_index = mat
+ f.use_smooth = smooth
+ return
+
+
+#
+# parseUvTexture(args, tokens, me):
+# parseUvTexData(args, tokens, uvdata):
+#
+
+def parseUvTexture(args, tokens, me):
+ name = args[0]
+ me.uv_textures.new(name = name)
+ uvtex = me.uv_textures[-1]
+ 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)
+ vcol = me.vertex_colors.new(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.vertex_groups.new(grpName)
+ loadedData['VertexGroup'][grpName] = group
+ for (key, val, sub) in tokens:
+ if key == 'wv':
+ ob.vertex_groups.assign( [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):
+ 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, val, sub)
+ ob.active_shape_key_index = 0
+ return
+
+
+def parseShapeKey(ob, me, args, tokens):
+ if verbosity > 2:
+ 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):
+ skey = ob.shape_key_add(name=name, from_mix=False)
+ 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] += theScale*float(val[1])
+ pt[1] += theScale*float(val[2])
+ pt[2] += theScale*float(val[3])
+ else:
+ defaultKey(key, val, sub, "skey", [], globals(), locals())
+
+ return
+
+
+#
+# parseArmature (obName, args, tokens)
+#
+
+def parseArmature (args, tokens):
+ global toggle
+ if verbosity > 2:
+ print( "Parsing armature %s" % args )
+
+ amtname = args[0]
+ obname = args[1]
+ mode = args[2]
+
+ if mode == 'Rigify':
+ toggle |= T_Rigify
+ return parseRigify(amtname, obname, tokens)
+
+ toggle &= ~T_Rigify
+ 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, 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, sub, heads, tails)
+ else:
+ defaultKey(key, val, sub, "amt", ['MetaRig'], globals(), locals())
+ bpy.ops.object.mode_set(mode='OBJECT')
+ return amt
+
+#
+# parseBone(bone, amt, tokens, heads, tails):
+#
+
+def parseBone(bone, amt, tokens, heads, tails):
+ global todo
+
+ for (key, val, sub) in tokens:
+ if key == "head":
+ bone.head = (theScale*float(val[0]), theScale*float(val[1]), theScale*float(val[2]))
+ elif key == "tail":
+ bone.tail = (theScale*float(val[0]), theScale*float(val[1]), theScale*float(val[2]))
+ #elif key == 'restrict_select':
+ # pass
+ elif key == 'hide' and val[0] == 'True':
+ name = bone.name
+ '''
+ #bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = amt.bones[name]
+ pbone.hide = True
+ print("Hide", pbone, pbone.hide)
+ #bpy.ops.object.mode_set(mode='EDIT')
+ '''
+ 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
+ print( "Parsing bonegroup %s" % args )
+ name = args[0]
+ bpy.ops.pose.group_add()
+ print(dir(pose.bone_groups))
+ bg = pose.bone_groups.active
+ 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
+ if invalid(args[1]):
+ return
+ name = args[0]
+ pb = pbones[name]
+ amt = ob.data
+
+ # Make posebone active - don't know how to do this in pose mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+ amt.bones.active = amt.bones[name]
+ 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':
+ bpy.ops.object.mode_set(mode='OBJECT')
+ amt.bones.active = amt.bones[name]
+ ob.constraints.active = cns
+ expr = "bpy.ops.%s" % val[0]
+ # print(expr)
+ exec(expr)
+ bpy.ops.object.mode_set(mode='POSE')
+ 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)
+ elif key == 'hide':
+ #bpy.ops.object.mode_set(mode='OBJECT')
+ amt.bones[name].hide = eval(val[0])
+ #bpy.ops.object.mode_set(mode='POSE')
+
+ 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, ["lock_location_x", "lock_location_y", "lock_location_z"], val)
+ elif key == 'rot_lock':
+ parseArray(cns, ["lock_rotation_x", "lock_rotation_y", "lock_rotation_z"], val)
+ else:
+ defaultKey(key, val, sub, "cns", [], globals(), locals())
+
+
+ #print("cns %s done" % cns.name)
+ return cns
+
+#
+
+
+# parseCurve (args, tokens):
+# parseSpline(cu, args, tokens):
+# parseBezier(spline, n, args, tokens):
+#
+
+def parseCurve (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing curve %s" % args )
+ bpy.ops.object.add(type='CURVE')
+ cu = setObjectAndData(args, 'Curve')
+
+ for (key, val, sub) in tokens:
+ if key == 'Spline':
+ parseSpline(cu, val, sub)
+ else:
+ defaultKey(key, val, sub, "cu", [], globals(), locals())
+ return
+
+def parseTextCurve (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing text curve %s" % args )
+ bpy.ops.object.text_add()
+ txt = setObjectAndData(args, 'Text')
+
+ for (key, val, sub) in tokens:
+ if key == 'Spline':
+ parseSpline(txt, val, sub)
+ elif key == 'BodyFormat':
+ parseCollection(txt.body_format, sub, [])
+ elif key == 'EditFormat':
+ parseDefault(txt.edit_format, sub, {}, [])
+ elif key == 'Font':
+ parseDefault(txt.font, sub, {}, [])
+ elif key == 'TextBox':
+ parseCollection(txt.body_format, sub, [])
+ else:
+ defaultKey(key, val, sub, "txt", [], globals(), locals())
+ return
+
+
+def parseSpline(cu, args, tokens):
+ typ = args[0]
+ spline = cu.splines.new(typ)
+ nPointsU = int(args[1])
+ nPointsV = int(args[2])
+ #spline.point_count_u = nPointsU
+ #spline.point_count_v = nPointsV
+ if typ == 'BEZIER' or typ == 'BSPLINE':
+ spline.bezier_points.add(nPointsU)
+ else:
+ spline.points.add(nPointsU)
+
+ n = 0
+ for (key, val, sub) in tokens:
+ if key == 'bz':
+ parseBezier(spline.bezier_points[n], val, sub)
+ n += 1
+ elif key == 'pt':
+ parsePoint(spline.points[n], val, sub)
+ n += 1
+ else:
+ defaultKey(key, val, sub, "spline", [], globals(), locals())
+ return
+
+def parseBezier(bez, args, tokens):
+ bez.co = eval(args[0])
+ bez.co = theScale*bez.co
+ bez.handle1 = eval(args[1])
+ bez.handle1_type = args[2]
+ bez.handle2 = eval(args[3])
+ bez.handle2_type = args[4]
+ return
+
+def parsePoint(pt, args, tokens):
+ pt.co = eval(args[0])
+ pt.co = theScale*pt.co
+ return
+
+#
+# parseLattice (args, tokens):
+#
+
+def parseLattice (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing lattice %s" % args )
+ bpy.ops.object.add(type='LATTICE')
+ lat = setObjectAndData(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 = theScale*x
+ v.y = theScale*y
+ v.z = theScale*z
+
+ v = points[n].deformed_co
+ (x,y,z) = eval(val[1])
+ v.x = theScale*x
+ v.y = theScale*y
+ v.z = theScale*z
+
+ n += 1
+ return
+
+#
+# parseLamp (args, tokens):
+# parseFalloffCurve(focu, args, tokens):
+#
+
+def parseLamp (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing lamp %s" % args )
+ bpy.ops.object.add(type='LAMP')
+ lamp = setObjectAndData(args, 'Lamp')
+ for (key, val, sub) in tokens:
+ if key == 'FalloffCurve':
+ parseFalloffCurve(lamp.falloff_curve, val, sub)
+ else:
+ defaultKey(key, val, sub, "lamp", [], globals(), locals())
+ return
+
+def parseFalloffCurve(focu, args, tokens):
+ return
+
+#
+# parseGroup (args, tokens):
+# parseGroupObjects(args, tokens, grp):
+#
+
+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
+
+#
+# parseWorld (args, tokens):
+#
+
+def parseWorld (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing world %s" % args )
+ world = bpy.context.scene.world
+ for (key, val, sub) in tokens:
+ if key == 'Lighting':
+ parseDefault(world.lighting, sub, {}, [])
+ elif key == 'Mist':
+ parseDefault(world.mist, sub, {}, [])
+ elif key == 'Stars':
+ parseDefault(world.stars, sub, {}, [])
+ else:
+ defaultKey(key, val, sub, "world", [], globals(), locals())
+ return
+
+#
+# parseScene (args, tokens):
+# parseRenderSettings(render, args, tokens):
+# parseToolSettings(tool, args, tokens):
+#
+
+def parseScene (args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing scene %s" % args )
+ scn = bpy.context.scene
+ for (key, val, sub) in tokens:
+ if key == 'NodeTree':
+ scn.use_nodes = True
+ parseNodeTree(scn, val, sub)
+ elif key == 'GameData':
+ parseDefault(scn.game_data, sub, {}, [])
+ elif key == 'KeyingSet':
+ pass
+ #parseDefault(scn.keying_sets, sub, {}, [])
+ elif key == 'ObjectBase':
+ pass
+ #parseDefault(scn.bases, sub, {}, [])
+ elif key == 'RenderSettings':
+ parseRenderSettings(scn.render, sub, [])
+ elif key == 'ToolSettings':
+ subkeys = {'ImagePaint' : "image_paint",
+ 'Sculpt' : "sculpt",
+ 'VertexPaint' : "vertex_paint",
+ 'WeightPaint' : "weight_paint" }
+ parseDefault(scn.tool_settings, sub, subkeys, [])
+ elif key == 'UnitSettings':
+ parseDefault(scn.unit_settings, sub, {}, [])
+ else:
+ defaultKey(key, val, sub, "scn", [], globals(), locals())
+ return
+
+def parseRenderSettings(render, args, tokens):
+ global todo
+ if verbosity > 2:
+ print( "Parsing RenderSettings %s" % args )
+ for (key, val, sub) in tokens:
+ if key == 'Layer':
+ pass
+ #parseDefault(scn.layers, sub, [])
+ else:
+ defaultKey(key, val, sub, "render", [], globals(), locals())
+ return
+
+#
+# postProcess()
+#
+
+def postProcess():
+ if not toggle & T_MHX:
+ return
+ try:
+ ob = loadedData['Object']['HumanMesh']
+ except:
+ ob = None
+ if toggle & T_Diamond == 0 and ob:
+ deleteDiamonds(ob)
+ if toggle & T_Rigify and False:
+ 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']['HumanMesh']
+ mod = ob.modifiers[0]
+ print(ob, mod, mod.object)
+ mod.object = rig
+ print("Rig changed", mod.object)
+ return
+
+#
+# deleteDiamonds(ob)
+# Delete joint diamonds in main mesh
+#
+
+def deleteDiamonds(ob):
+ bpy.context.scene.objects.active = ob
+ if not bpy.context.object:
+ return
+ print("Delete diamonds in %s" % bpy.context.object)
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='DESELECT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ me = ob.data
+ for f in me.faces:
+ if len(f.vertices) < 4:
+ for vn in f.vertices:
+ me.vertices[vn].select = True
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.delete(type='VERT')
+ bpy.ops.object.mode_set(mode='OBJECT')
+ return
+
+
+#
+# parseProcess(args, tokens):
+# applyTransform(objects, rig, parents):
+#
+
+def parseProcess(args, tokens):
+ if toggle & T_Bend == 0:
+ return
+ try:
+ rig = loadedData['Object'][args[0]]
+ except:
+ rig = None
+ if not rig:
+ return
+
+ parents = {}
+ objects = []
+
+ for (key, val, sub) in tokens:
+ #print(key, val)
+ 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':
+ axis = val[1]
+ angle = float(val[2])
+ mat = mathutils.Matrix.Rotation(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]
+ except:
+ print("No bone "+val[0])
+ pass
+ elif key == 'Snap':
+ try:
+ eb = ebones[val[0]]
+ except:
+ eb = None
+ tb = ebones[val[1]]
+ typ = val[2]
+ if eb == None:
+ pass
+ elif typ == 'Inv':
+ eb.head = tb.tail
+ eb.tail = tb.head
+ elif typ == 'Head':
+ eb.head = tb.head
+ elif typ == 'Tail':
+ eb.tail = tb.tail
+ elif typ == 'Both':
+ eb.head = tb.head
+ eb.tail = tb.tail
+ eb.roll = tb.roll
+ else:
+ raise NameError("Snap type %s" % typ)
+ elif key == 'PoseMode':
+ bpy.context.scene.objects.active = rig
+ bpy.ops.object.mode_set(mode='POSE')
+ pbones = rig.pose.bones
+ elif key == 'ObjectMode':
+ bpy.context.scene.objects.active = rig
+ bpy.ops.object.mode_set(mode='POSE')
+ pbones = rig.pose.bones
+ elif key == 'EditMode':
+ bpy.context.scene.objects.active = rig
+ bpy.ops.object.mode_set(mode='EDIT')
+ ebones = rig.data.edit_bones
+ bpy.ops.armature.select_all(action='DESELECT')
+ elif key == 'Roll':
+ try:
+ eb = ebones[val[0]]
+ except:
+ eb = None
+ if eb:
+ eb.roll = float(val[1])
+ elif key == 'Select':
+ pass
+ elif key == 'RollUp':
+ pass
+ elif key == 'Apply':
+ applyTransform(objects, rig, parents)
+ elif key == 'ApplyArmature':
+ try:
+ ob = loadedData['Object'][val[0]]
+ objects.append((ob,sub))
+ except:
+ ob = None
+ elif key == 'Object':
+ try:
+ ob = loadedData['Object'][val[0]]
+ 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)
+ return
+
+def applyTransform(objects, rig, parents):
+ for (ob,tokens) in objects:
+ print("Applying transform to %s" % ob)
+ bpy.context.scene.objects.active = ob
+ bpy.ops.object.visual_transform_apply()
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier='Armature')
+
+ bpy.context.scene.objects.active = rig
+ bpy.ops.object.mode_set(mode='POSE')
+ bpy.ops.pose.armature_apply()
+ bpy.ops.object.mode_set(mode='OBJECT')
+ 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.use_connect:
+ par.tail = eb.head
+ eb.parent = par
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ return
+
+#
+# defaultKey(ext, args, tokens, var, exclude, glbals, lcals):
+#
+
+def defaultKey(ext, args, tokens, var, exclude, glbals, lcals):
+ global todo
+
+ if ext == 'Property':
+ try:
+ expr = "%s['%s'] = %s" % (var, args[0], args[1])
+ except:
+ expr = None
+ # print("Property", expr)
+ if expr:
+ exec(expr, glbals, lcals)
+ 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:
+ pushOnTodoList(var, expr, glbals, lcals)
+ return
+
+#
+#
+#
+
+def pushOnTodoList(var, expr, glbals, lcals):
+ global todo
+ print("Tdo", var)
+ print(dir(eval(var, glbals, lcals)))
+ raise NameError("Todo", 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, subkeys, exclude):
+#
+
+def parseDefault(data, tokens, subkeys, exclude):
+ for (key, val, sub) in tokens:
+ if key in subkeys.keys():
+ for (key2, val2, sub2) in sub:
+ defaultKey(key2, val2, sub2, "data.%s" % subkeys[key], [], globals(), locals())
+ else:
+ defaultKey(key, val, sub, "data", exclude, globals(), locals())
+
+def parseCollection(data, tokens, exclude):
+ return
+
+
+#
+# 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" or ob.type == 'EMPTY':
+ 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 < 8:
+ 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="File path used for importing the MHX file", maxlen= 1024, default= "")
+
+ scale = FloatProperty(name="Scale", description="Default meter, decimeter = 1.0", default = theScale)
+
+
+
+ enforce = BoolProperty(name="Enforce version", description="Only accept MHX files of correct version", default=toggle&T_EnforceVersion)
+ mesh = BoolProperty(name="Mesh", description="Use main mesh", default=toggle&T_Mesh)
+ proxy = BoolProperty(name="Proxies", description="Use proxies", default=toggle&T_Proxy)
+ armature = BoolProperty(name="Armature", description="Use armature", default=toggle&T_Armature)
+ replace = BoolProperty(name="Replace scene", description="Replace scene", default=toggle&T_Replace)
+ cage = BoolProperty(name="Cage", description="Load mesh deform cage", default=toggle&T_Cage)
+ clothes = BoolProperty(name="Clothes", description="Include clothes", default=toggle&T_Clothes)
+ stretch = BoolProperty(name="Stretchy limbs", description="Stretchy limbs", default=toggle&T_Stretch)
+ 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)
+ diamond = BoolProperty(name="Diamonds", description="Keep joint diamonds", default=toggle&T_Diamond)
+ bend = BoolProperty(name="Bend joints", description="Bend joints for better IK", default=toggle&T_Bend)
+
+ def execute(self, context):
+ global toggle
+ O_EnforceVersion = T_EnforceVersion if self.properties.enforce else 0
+ O_Mesh = T_Mesh if self.properties.mesh else 0
+ O_Proxy = T_Proxy if self.properties.proxy else 0
+ O_Armature = T_Armature if self.properties.armature else 0
+ O_Replace = T_Replace if self.properties.replace else 0
+ O_Cage = T_Cage if self.properties.cage else 0
+ O_Clothes = T_Clothes if self.properties.clothes else 0
+ O_Stretch = T_Stretch if self.properties.stretch 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_Diamond = T_Diamond if self.properties.diamond else 0
+ O_Bend = T_Bend if self.properties.bend else 0
+ toggle = ( O_EnforceVersion | O_Mesh | O_Proxy | O_Armature | O_Replace | O_Stretch | O_Cage |
+ O_Face | O_Shape | O_Symm | O_Diamond | O_Bend | O_Clothes | T_MHX )
+
+ print("Load", self.properties.filepath)
+ readMhxFile(self.properties.filepath, self.properties.scale)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ context.window_manager.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+def menu_func(self, context):
+ self.layout.operator(IMPORT_OT_makehuman_mhx.bl_idname, text="MakeHuman (.mhx)...")
+
+def register():
+ bpy.types.INFO_MT_file_import.append(menu_func)
+
+def unregister():
+ bpy.types.INFO_MT_file_import.remove(menu_func)
+
+if __name__ == "__main__":
+ try:
+ unregister()
+ except:
+ pass
+ register()
+
+#
+# Testing
+#
+"""
+#readMhxFile("C:/Documents and Settings/xxxxxxxxxxxxxxxxxxxx/Mina dokument/makehuman/exports/foo-25.mhx", 'Classic')
+readMhxFile("/home/thomas/makehuman/exports/foo-25.mhx", 1.0)
+
+#toggle = T_Replace + T_Mesh + T_Armature + T_MHX
+#readMhxFile("/home/thomas/myblends/test.mhx", 1.0)
+"""
+
+
+
+
diff --git a/io_import_scene_unreal_psk.py b/io_import_scene_unreal_psk.py
new file mode 100644
index 00000000..a115bc22
--- /dev/null
+++ b/io_import_scene_unreal_psk.py
@@ -0,0 +1,602 @@
+# ##### 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 Unreal Skeleton Mesh(.psk)",
+ "author": "Darknet",
+ "version": (2, 0),
+ "blender": (2, 5, 3),
+ "api": 31847,
+ "location": "File > Import ",
+ "description": "Import Unreal Engine (.psk)",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
+ "Scripts/Import-Export/Unreal_psk_psa",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=21366&group_id=153&atid=469",
+ "category": "Import-Export"}
+
+"""
+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 )
+"""
+
+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.vertices.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.vertices.add(len(verts))
+ me_ob.faces.add(len(faces)//4)
+
+ me_ob.vertices.foreach_set("co", unpack_list(verts))
+
+ me_ob.faces.foreach_set("vertices_raw", faces)
+ me_ob.faces.foreach_set("use_smooth", [False] * len(me_ob.faces))
+ me_ob.update()
+
+ #===================================================================================================
+ #UV Setup
+ #===================================================================================================
+ texture = []
+ texturename = "text1"
+ #print(dir(bpy.data))
+ if (len(faceuv) > 0):
+ uvtex = me_ob.uv_textures.new() #add one uv texture
+ for i, face in enumerate(me_ob.faces):
+ blender_tface= uvtex.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(uvtex)
+
+ #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.materials.append(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.filepath)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ wm.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+def menu_func(self, context):
+ self.layout.operator(IMPORT_OT_psk.bl_idname, text="Skeleton Mesh (.psk)")
+
+
+def register():
+ bpy.types.INFO_MT_file_import.append(menu_func)
+
+def unregister():
+ 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..7e748d1b
--- /dev/null
+++ b/io_mesh_raw/__init__.py
@@ -0,0 +1,64 @@
+# ##### 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": "Raw mesh",
+ "author": "Anthony D,Agostino (Scorpius), Aurel Wildfellner",
+ "version": (0, 2),
+ "blender": (2, 5, 3),
+ "api": 31667,
+ "location": "File > Import-Export > Raw faces ",
+ "description": "Import Raw Faces (.raw format)",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/Raw_Mesh_IO",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=21733&group_id=153&atid=469",
+ "category": "Import-Export"}
+
+if "bpy" in locals():
+ import imp
+ imp.reload(import_raw)
+ imp.reload(export_raw)
+else:
+ from . import import_raw
+ from . import export_raw
+
+
+import bpy
+
+def menu_import(self, context):
+ self.layout.operator(import_raw.RawImporter.bl_idname, text="Raw Faces (.raw)").filepath = "*.raw"
+
+
+def menu_export(self, context):
+ 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():
+ bpy.types.INFO_MT_file_import.append(menu_import)
+ bpy.types.INFO_MT_file_export.append(menu_export)
+
+def unregister():
+ 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..056c4b93
--- /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.vertices_raw:
+ fv.append(matrix * mesh.vertices[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(bpy.context.scene, 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= "", subtype='FILE_PATH')
+ 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.filepath, self.apply_modifiers, self.triangulate)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ wm.fileselect_add(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..e78e5f39
--- /dev/null
+++ b/io_mesh_raw/import_raw.py
@@ -0,0 +1,142 @@
+# ##### 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 io_utils 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_tot = 0
+
+ for f in faces:
+ for i, v in enumerate(f):
+ index = coords.get(v)
+
+ if index is None:
+ index = coords[v] = index_tot
+ index_tot += 1
+ verts.append(v)
+
+ fi[i] = index
+
+ mesh = bpy.data.meshes.new(objName)
+ mesh.vertices.add(len(verts))
+ mesh.faces.add(len(faces))
+ mesh.vertices.foreach_set("co", unpack_list(verts))
+ mesh.faces.foreach_set("vertices_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 is 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="", subtype='FILE_PATH')
+
+ def execute(self, context):
+
+ #convert the filename to an object name
+ objName = bpy.path.display_name(self.filename.split("\\")[-1].split("/")[-1])
+
+ mesh = readMesh(self.filepath, objName)
+ addMeshObj(mesh, objName)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ wm.fileselect_add(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..4b03b286
--- /dev/null
+++ b/io_mesh_stl/__init__.py
@@ -0,0 +1,142 @@
+# ##### 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": "STL format",
+ "author": "Guillaume Bouchard (Guillaum)",
+ "version": (1, ),
+ "blender": (2, 5, 3),
+ "api": 31667,
+ "location": "File > Import-Export > Stl",
+ "description": "Import-Export STL files",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/STL",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"
+ "func=detail&aid=22837&group_id=153&atid=469",
+ "category": "Import-Export"}
+
+# @todo write the wiki page
+
+"""
+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
+"""
+
+if "bpy" in locals():
+ import imp
+ imp.reload(stl_utils)
+ imp.reload(blender_utils)
+else:
+ from . import stl_utils
+ from . import blender_utils
+
+import itertools
+import os
+
+import bpy
+from bpy.props import *
+from io_utils import ExportHelper, ImportHelper
+
+
+class StlImporter(bpy.types.Operator, ImportHelper):
+ '''
+ Load STL triangle mesh data
+ '''
+ bl_idname = "import_mesh.stl"
+ bl_label = "Import STL"
+
+ filename_ext = ".stl"
+
+ files = CollectionProperty(name="File Path",
+ description="File path used for importing "
+ "the STL file",
+ type=bpy.types.OperatorFileListElement)
+
+ directory = StringProperty()
+
+ def execute(self, context):
+ paths = (os.path.join(self.directory, name.name) for name in self.files)
+
+ for path in paths:
+ objName = bpy.path.display_name(path.split("\\")[-1].split("/")[-1])
+ tris, pts = stl_utils.read_stl(path)
+
+ blender_utils.create_and_link_mesh(objName, tris, pts)
+
+ return {'FINISHED'}
+
+
+class StlExporter(bpy.types.Operator, ExportHelper):
+ '''
+ Save STL triangle mesh data from the active object
+ '''
+ bl_idname = "export_mesh.stl"
+ bl_label = "Export STL"
+
+ filename_ext = ".stl"
+
+ 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):
+ faces = itertools.chain.from_iterable(
+ blender_utils.faces_from_mesh(ob, self.apply_modifiers)
+ for ob in context.selected_objects)
+
+ stl_utils.write_stl(self.filepath, faces, self.ascii)
+
+ return {'FINISHED'}
+
+
+def menu_import(self, context):
+ self.layout.operator(StlImporter.bl_idname,
+ text="Stl (.stl)").filepath = "*.stl"
+
+
+def menu_export(self, context):
+ 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.INFO_MT_file_import.append(menu_import)
+ bpy.types.INFO_MT_file_export.append(menu_export)
+
+
+def unregister():
+ 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..a043a8f3
--- /dev/null
+++ b/io_mesh_stl/blender_utils.py
@@ -0,0 +1,53 @@
+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
+ try:
+ mesh = ob.create_mesh(bpy.context.scene,
+ apply_modifier, "PREVIEW")
+ except SystemError:
+ return ()
+
+ 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.vertices) == 4:
+ yield face.vertices[:3]
+ yield face.vertices[2:] + [face.vertices[0]]
+ else:
+ yield list(face.vertices)
+
+ return ([tuple(mesh.vertices[index].co * ob.matrix_world)
+ 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..734204b8
--- /dev/null
+++ b/io_mesh_stl/stl_utils.py
@@ -0,0 +1,242 @@
+'''
+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
+
+BINARY_HEADER = 80
+BINARY_STRIDE = 12 * 4 + 2
+
+def _is_ascii_file(data):
+ '''
+ This function returns True if the data represents an ASCII file.
+
+ Please note that a False value does not necessary means that the data
+ represents a binary file. It can be a (very *RARE* in real life, but
+ can easily be forged) ascii file.
+ '''
+ size = struct.unpack_from('<I', data, BINARY_HEADER)[0]
+
+ return not data.size() == BINARY_HEADER + 4 + BINARY_STRIDE * size
+
+def _binary_read(data):
+ # an stl binary file is
+ # - 80 bytes of description
+ # - 4 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 = BINARY_HEADER + 4 + 12
+
+ # read header size, ignore description
+ size = struct.unpack_from('<I', data, BINARY_HEADER)[0]
+ unpack = struct.Struct('<9f').unpack_from
+
+ for i in range(size):
+ # read the points coordinates of each triangle
+ pt = unpack(data, OFFSET + BINARY_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 _is_ascii_file(data) 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.path.display_name(filename)
+ tris, pts = read_stl(filename)
+
+ blender_utils.create_and_link_mesh(objName, tris, pts)
diff --git a/io_scene_m3/__init__.py b/io_scene_m3/__init__.py
new file mode 100644
index 00000000..97365f55
--- /dev/null
+++ b/io_scene_m3/__init__.py
@@ -0,0 +1,101 @@
+# ##### 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": "M3 Import",
+ "author": "Cory Perry (muraj)",
+ "version": (0, 0, 2),
+ "blender": (2, 5, 4),
+ "api": 31878,
+ "location": "File > Import-Export > M3 Import ",
+ "description": "Import Blizzard M3 models (.m3 format)",
+ "warning": "Alpha version",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/M3_Import",
+ "tracker_url": "http://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=24017&group_id=153&atid=469",
+ "category": "Import-Export"}
+
+"""
+This script imports m3 format files to Blender.
+
+The m3 file format, used by Blizzard in several games, is based around the mdx and m2 file format. Thanks to the efforts of Volcore, madyavic and the people working on libm3, the file format has been reversed engineered enough to make this script possible (Thanks guys!).
+
+This script currently imports the following:
+- Geometry data (vertices, faces, submeshes [in vertex groups])
+- Model Textures (currently only the first material is supported)
+
+ Blender supports the DDS file format and needs the image in the same
+ directory. This script will notify you of any missing textures.
+
+TODO:
+- Documentation & clean up
+- Import *ALL* materials and bind accordingly
+- Bind specular, normal, and emissive maps following new API
+- Adjust vertices to bind pose (import IREF matrices)
+- Import Armature data
+- Get acquainted with 2.54 Armature API.
+- Import Animation data
+
+Known Bugs:
+ Thor isn't parsable for some reason, will look into.
+
+Usage:
+ Execute this script from the "File->Import" menu and choose a m3 file to open.
+
+Notes:
+ Generates the standard verts and faces lists.
+
+"""
+
+if "bpy" in locals():
+ import imp
+ imp.reload(import_m3)
+ #imp.reload(export_m3)
+else:
+ from . import import_m3
+ #from . import export_m3
+
+import bpy
+
+def menu_import(self, context):
+ from io_scene_pmd import import_pmd
+ self.layout.operator(
+ import_m3.M3Importer.bl_idname,
+ text="Blizzard M3 (.m3)",
+ icon='PLUGIN'
+ )
+
+
+#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():
+ bpy.types.INFO_MT_file_import.append(menu_import)
+ # bpy.types.INFO_MT_file_export.append(menu_export)
+
+def unregister():
+ 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_scene_m3/import_m3.py b/io_scene_m3/import_m3.py
new file mode 100644
index 00000000..c2d15e1b
--- /dev/null
+++ b/io_scene_m3/import_m3.py
@@ -0,0 +1,308 @@
+# ##### 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, mathutils, time, struct, os.path
+from bpy.props import *
+
+##################
+## Struct setup ##
+##################
+
+verFlag = False #Version flag (MD34 == True, MD33 == False)
+
+class ref:
+ fmt = 'LL'
+ def __init__(self, file):
+ global verFlag
+ if verFlag: self.fmt += 'L' #Extra unknown...
+ _s=file.read(struct.calcsize(self.fmt))
+ self.entries, self.refid = struct.unpack(self.fmt, _s)[:2]
+ @classmethod
+ def size(cls):
+ global verFlag
+ return struct.calcsize(cls.fmt + ('L' if verFlag else ''))
+
+
+class animref:
+ fmt = 'HHL'
+ def __init__(self, file):
+ _s=file.read(struct.calcsize(self.fmt))
+ self.flags, self.animflags, self.animid = struct.unpack(self.fmt, _s)
+
+
+class Tag:
+ fmt = '4sLLL'
+ def __init__(self, file):
+ _s=file.read(struct.calcsize(self.fmt))
+ self.name, self.ofs, self.nTag, self.version = struct.unpack(self.fmt, _s)
+
+
+class matrix:
+ fmt='f'*16
+ def __init__(self, file):
+ _s=file.read(struct.calcsize(self.fmt))
+ self.mat = struct.unpack(self.fmt, _s)
+
+
+class vect:
+ fmt='fff'
+ def __init__(self, file):
+ _s=file.read(struct.calcsize(self.fmt))
+ self.v = struct.unpack(self.fmt, _s)
+
+
+class vertex:
+ fmt="4B4b4B%dH4B"
+ ver = { 0x020000: 2, 0x060000: 4, 0x0A0000: 6, 0x120000: 8 }
+ def __init__(self, file, flag):
+ self.pos = vect(file)
+ fmt = self.fmt % (self.ver[flag])
+ _s=file.read(struct.calcsize(fmt))
+ print(file.tell())
+ _s = struct.unpack(fmt, _s)
+ self.boneWeight = _s[0:4]
+ self.boneIndex = _s[4:8]
+ self.normal = _s[8:12]
+ self.uv = _s[12:14]
+ self.tan = _s[-4:] #Skipping the middle ukn value if needed
+ self.boneWeight = [b/255.0 for b in self.boneWeight]
+ self.normal = [x*2.0/255.0-1.0 for x in self.normal]
+ self.tan = [x*2.0/255.0-1.0 for x in self.tan]
+ self.uv = [x/2046.0 for x in self.uv]
+ self.uv[1] = 1.0 - self.uv[1]
+ @classmethod
+ def size(cls, flag):
+ return struct.calcsize(cls.fmt % (cls.ver[flag]))
+
+
+class quat:
+ fmt='ffff'
+ def __init__(self, file):
+ _s=file.read(struct.calcsize(self.fmt))
+ self.v = struct.unpack(self.fmt, _s)
+ self.v = [self.v[-1], self.v[0], self.v[1], self.v[2]] #Quats are stored x,y,z,w - this fixes it
+
+
+class bone:
+ def __init__(self, file):
+ file.read(4) #ukn1
+ self.name = ref(file)
+ self.flag, self.parent, _ = struct.unpack('LhH',file.read(8))
+ self.posid = animref(file)
+ self.pos = vect(file)
+ file.read(4*4) #ukn
+ self.rotid = animref(file)
+ self.rot = quat(file)
+ file.read(4*5) #ukn
+ self.scaleid = animref(file)
+ self.scale = vect(file)
+ vect(file) #ukn
+ file.read(4*6) #ukn
+
+
+class div:
+ def __init__(self, file):
+ self.faces = ref(file)
+ self.regn = ref(file)
+ self.bat = ref(file)
+ self.msec = ref(file)
+ file.read(4) #ukn
+
+
+class regn:
+ fmt = 'LHHLL6H'
+ def __init__(self, file):
+ _s=file.read(struct.calcsize(self.fmt))
+ _ukn1, self.ofsVert, self.nVerts, self.ofsIndex, self.nIndex, \
+ self.boneCount, self.indBone, self.nBone = struct.unpack(self.fmt, _s)[:8]
+
+
+class mat:
+ def __init__(self, file):
+ self.name = ref(file)
+ file.read(4*10) #ukn
+ self.layers = [ref(file) for _ in range(13)]
+ file.read(4*15) #ukn
+
+
+class layr:
+ def __init__(self, file):
+ file.read(4)
+ self.name = ref(file)
+ #Rest not implemented.
+
+
+class hdr:
+ fmt = '4sLL'
+ def __init__(self, file):
+ _s = file.read(struct.calcsize(self.fmt))
+ self.magic, self.ofsTag, self.nTag = struct.unpack(self.fmt, _s)
+ self.MODLref = ref(file)
+
+
+class MODL:
+ def __init__(self, file, flag=20):
+ global verFlag
+ self.name = ref(file)
+ self.ver = struct.unpack('L',file.read(4))[0]
+ self.seqHdr = ref(file)
+ self.seqData = ref(file)
+ self.seqLookup = ref(file)
+ file.read(0x1C if verFlag else 0x14) #ukn1
+ self.bones = ref(file)
+ file.read(4) #ukn2
+ self.flags = struct.unpack('L',file.read(4))[0]
+ self.vert = ref(file)
+ self.views = ref(file)
+ self.boneLookup = ref(file)
+ self.extents = [vect(file), vect(file)]
+ self.radius = struct.unpack('f',file.read(4))[0]
+ if verFlag: file.read(4) #ukn MD34 addition
+ if not verFlag:
+ if flag == 20: file.read(0x2C)
+ else: file.read(0x34)
+ else:
+ if flag == 20: file.read(0x30)
+ else: file.read(0x3C)
+ self.attach = ref(file)
+ file.read(5*ref.size())
+ self.materialsLookup = ref(file)
+ self.materials = ref(file)
+ file.read(ref.size())
+ if not verFlag:
+ file.read(0x90)
+ else: file.read(0xD8)
+ self.iref = ref(file)
+
+
+def read(file, context, op):
+ '''Imports as an m3 file'''
+
+ global verFlag
+ h=hdr(file)
+ if h.magic == b'43DM':
+ print('m3_import: !WARNING! MD34 files not full tested...')
+ verFlag = True
+ elif h.magic == b'33DM':
+ verFlag = False
+ else:
+ raise Exception('m3_import: !ERROR! Not a valid or supported m3 file')
+ file.seek(h.ofsTag) #Jump to the Tag table
+ print('m3_import: !INFO! Reading TagTable...')
+ tagTable = [Tag(file) for _ in range(h.nTag)]
+ file.seek(tagTable[h.MODLref.refid].ofs)
+ m = MODL(file, tagTable[h.MODLref.refid].version)
+ if not m.flags & 0x20000:
+ raise Exception('m3_import: !ERROR! Model doesn\'t contain any vertices')
+ vert_flags = m.flags & 0x1E0000 #Mask out the vertex version
+ file.seek(tagTable[m.vert.refid].ofs)
+ print('m3_import: !INFO! Reading Vertices...')
+ verts = [ vertex(file, vert_flags) for _ in range(int(tagTable[m.vert.refid].nTag / vertex.size(vert_flags))) ]
+ file.seek(tagTable[m.views.refid].ofs)
+ d = div(file)
+ file.seek(tagTable[d.faces.refid].ofs)
+ print('m3_import: !INFO! Reading Faces...')
+ rawfaceTable = struct.unpack('H'*(tagTable[d.faces.refid].nTag), file.read(tagTable[d.faces.refid].nTag*2))
+ faceTable = []
+ for i in range(1, len(rawfaceTable)+1):
+ faceTable.append(rawfaceTable[i-1])
+ if i % 3 == 0: faceTable.append(0) #Add a zero for the fourth index to the face.
+ print('m3_import: !INFO! Adding Geometry...')
+ mesh = bpy.data.meshes.new(os.path.basename(op.properties.filepath))
+ mobj = bpy.data.objects.new(os.path.basename(op.properties.filepath), mesh)
+ context.scene.objects.link(mobj)
+ v = []
+ for vert in verts: #"Flatten" the vertex array...
+ v.extend(vert.pos.v)
+ mesh.vertices.add(len(verts))
+ mesh.faces.add(len(rawfaceTable)//3)
+ mesh.vertices.foreach_set('co', v)
+ mesh.faces.foreach_set('vertices_raw', faceTable)
+ uvtex = mesh.uv_textures.new()
+ for i, face in enumerate(mesh.faces):
+ uf = uvtex.data[i]
+ uf.uv1 = verts[faceTable[i*4+0]].uv
+ uf.uv2 = verts[faceTable[i*4+1]].uv
+ uf.uv3 = verts[faceTable[i*4+2]].uv
+ uf.uv4 = (0,0)
+ print('m3_import: !INFO! Importing materials...')
+ material = bpy.data.materials.new('Mat00')
+ mesh.materials.append(material)
+ file.seek(tagTable[m.materials.refid].ofs)
+ mm = mat(file)
+ for map, i in [('use_map_diffuse', 0), ('use_map_specular', 2), ('use_map_normal', 9)]:
+ file.seek(tagTable[mm.layers[i].refid].ofs)
+ nref = layr(file).name
+ file.seek(tagTable[nref.refid].ofs)
+ name = bytes.decode(file.read(nref.entries-1))
+ path = os.path.join(os.path.dirname(op.properties.filepath), os.path.basename(str(name)))
+ tex = bpy.data.textures.new(name=os.path.basename(path), type='IMAGE')
+ if os.path.exists(path):
+ tex.image = bpy.data.images.load(path)
+ print("m3_import: !INFO! Loaded %s" % (path))
+ else:
+ print("m3_import: !WARNING! Cannot find texture \"%s\"" % (path))
+ mtex = material.texture_slots.add()
+ mtex.texture = tex
+ mtex.texture_coords = 'UV'
+ mtex.use_map_color_diffuse = (i == 0)
+ setattr(mtex, map, True) # <- This is really dumb to have to do, just expose a bitmask or something.
+
+
+class M3Importer(bpy.types.Operator):
+ '''Import from M3 file format (.m3)'''
+
+ bl_idname = "import_mesh.blizzard_m3"
+ bl_label = 'Import M3'
+
+ # 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 M3 file", maxlen= 1024, default= "")
+
+ def execute(self, context):
+ t = time.clock()
+ with open(self.properties.filepath, 'rb') as file:
+ print('Importing file', self.properties.filepath)
+ read(file, context, self)
+ print('Finished importing in', time.clock() - t, 'seconds')
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ wm.fileselect_add(self)
+ return {'RUNNING_MODAL'}
+
+
+def menu_func(self, context):
+ self.layout.operator(M3Importer.bl_idname, text="M3 (.m3)")
+
+
+def register():
+ bpy.types.register(M3Importer)
+ bpy.types.INFO_MT_file_import.append(menu_func)
+
+
+def unregister():
+ bpy.types.unregister(M3Importer)
+ bpy.types.INFO_MT_file_import.remove(menu_func)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/mesh_relax.py b/mesh_relax.py
new file mode 100644
index 00000000..a9e476b7
--- /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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_addon_info = {
+ "name": "Relax",
+ "author": "Fabian Fricke",
+ "version": (1,1),
+ "blender": (2, 5, 3),
+ "api": 31847,
+ "location": "View3D > Specials > Relax ",
+ "description": "Relax the selected verts while retaining the shape",
+ "warning": "",
+ "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"}
+
+"""
+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
+"""
+
+
+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)
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return (obj and obj.type == 'MESH')
+
+ def execute(self, context):
+ for i in range(0,self.iterations):
+ relax_mesh(context)
+ return {'FINISHED'}
+
+
+def menu_func(self, context):
+ self.layout.operator(Relax.bl_idname, text="Relax")
+
+
+def register():
+ bpy.types.VIEW3D_MT_edit_mesh_specials.append(menu_func)
+ bpy.types.VIEW3D_MT_edit_mesh_vertices.append(menu_func)
+
+def unregister():
+ 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..80cd6c04
--- /dev/null
+++ b/mesh_surface_sketch.py
@@ -0,0 +1,824 @@
+# ##### 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": "Surface Sketch",
+ "author": "Eclectiel",
+ "version": (0,8),
+ "blender": (2, 5, 3),
+ "api": 31847,
+ "location": "View3D > EditMode > ToolShelf",
+ "description": "Draw meshes and re-topologies with Grease Pencil",
+ "warning": "Beta",
+ "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 *
+
+
+class VIEW3D_PT_tools_SURF_SKETCH(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+
+ bl_context = "mesh_edit"
+ bl_label = "Surface Sketching"
+
+ @classmethod
+ def poll(cls, 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.vertices[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].vertices[0] == prev_v and ob.data.edges[i].vertices[1] in all_selected_verts_idx:
+ verts_ordered.append(self.main_object.data.vertices[ob.data.edges[i].vertices[1]])
+ prev_v = ob.data.edges[i].vertices[1]
+ prev_ed = ob.data.edges[i]
+ elif ob.data.edges[i] != prev_ed and ob.data.edges[i].vertices[1] == prev_v and ob.data.edges[i].vertices[0] in all_selected_verts_idx:
+ verts_ordered.append(self.main_object.data.vertices[ob.data.edges[i].vertices[0]])
+ prev_v = ob.data.edges[i].vertices[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.vertices[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.vertices[0] in all_selected_verts:
+ all_selected_verts.append(self.main_object.data.vertices[ed.vertices[0]])
+ if not ed.vertices[1] in all_selected_verts:
+ all_selected_verts.append(self.main_object.data.vertices[ed.vertices[1]])
+
+ # All verts (both from each edge) to determine later which are at the tips (those not repeated twice).
+ all_verts_idx.append(ed.vertices[0])
+ all_verts_idx.append(ed.vertices[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.vertices[0] in all_chains_tips_idx or ed.vertices[1] in all_chains_tips_idx) and not (ed.vertices[0] in all_verts_idx and ed.vertices[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.vertices[0] in all_verts_idx and (((ed_tips.vertices[1] == ed_tips_b.vertices[0]) or ed_tips.vertices[1] == ed_tips_b.vertices[1])):
+ middle_vertex_idx = ed_tips.vertices[1]
+ tips_to_discard_idx.append(ed_tips.vertices[0])
+ elif ed_tips.vertices[1] in all_verts_idx and (((ed_tips.vertices[0] == ed_tips_b.vertices[0]) or ed_tips.vertices[0] == ed_tips_b.vertices[1])):
+ middle_vertex_idx = ed_tips.vertices[0]
+ tips_to_discard_idx.append(ed_tips.vertices[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.vertices[verts_tips_parsed_idx[0]].co)
+ points_A.append(self.main_object.data.vertices[middle_vertex_idx].co)
+
+ points_B.append(self.main_object.data.vertices[verts_tips_parsed_idx[1]].co)
+ points_B.append(self.main_object.data.vertices[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.vertices[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.vertices[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.vertices[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.vertices[self.main_object.data.edges[i].vertices[0]].co, self.main_object.data.vertices[self.main_object.data.edges[i].vertices[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.vertices[verts_tips_same_chain_idx[nearest_tip_first_st_first_pt_idx][0]].co)
+ points_tips.append(self.main_object.data.vertices[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.vertices.add(1)
+ last_v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
+ last_v.co = verts_ordered_U[i].co
+
+ vert_num_in_spline += 1
+
+ for sp in sketched_splines_parsed:
+ ob_ctrl_pts.data.vertices.add(1)
+ v = ob_ctrl_pts.data.vertices[len(ob_ctrl_pts.data.vertices) - 1]
+ v.co = sp[i]
+
+ if vert_num_in_spline > 1:
+ ob_ctrl_pts.data.edges.add(1)
+ ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[0] = len(ob_ctrl_pts.data.vertices) - 2
+ ob_ctrl_pts.data.edges[len(ob_ctrl_pts.data.edges) - 1].vertices[1] = len(ob_ctrl_pts.data.vertices) - 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].show_handles = False
+ bpy.data.curves[curve_crv.name].show_normal_face = False
+
+
+ def invoke (self, context, event):
+ self.main_object = bpy.context.object
+
+
+ self.execute(context)
+
+ return {"FINISHED"}
+
+
+def register():
+ bpy.types.Scene.SURFSK_edges_U = bpy.props.IntProperty(name="Cross", description="Number of edge rings crossing the strokes (perpendicular to strokes direction)", default=10, min=0, max=100000)
+ bpy.types.Scene.SURFSK_edges_V = bpy.props.IntProperty(name="Follow", description="Number of edge rings following the strokes (parallel to strokes direction)", default=10, min=0, max=100000)
+ bpy.types.Scene.SURFSK_precision = bpy.props.IntProperty(name="Precision", description="Precision level of the surface calculation", default=4, min=0, max=100000)
+ bpy.types.Scene.SURFSK_keep_strokes = bpy.props.BoolProperty(name="Keep strokes", description="Keeps the sketched strokes after adding the surface", default=False)
+
+ kc = bpy.data.window_managers[0].keyconfigs.default
+ km = kc.keymaps.get("3D View")
+ if km is None:
+ km = kc.keymaps.new(name="3D View")
+ keymap_item_add_surf = km.items.new("GPENCIL_OT_SURFSK_add_surface","E","PRESS", key_modifier="D")
+ keymap_item_stroke_to_curve = km.items.new("GPENCIL_OT_SURFSK_strokes_to_curves","C","PRESS", key_modifier="D")
+
+
+def unregister():
+ del bpy.types.Scene.SURFSK_edges_U
+ del bpy.types.Scene.SURFSK_edges_V
+ del bpy.types.Scene.SURFSK_precision
+ del bpy.types.Scene.SURFSK_keep_strokes
+
+ kc = bpy.data.window_managers[0].keyconfigs.default
+ km = kc.keymaps["3D View"]
+ for kmi in km.items:
+ if kmi.idname == 'wm.call_menu':
+ if kmi.properties.name == "GPENCIL_OT_SURFSK_add_surface":
+ km.items.remove(kmi)
+ elif kmi.properties.name == "GPENCIL_OT_SURFSK_strokes_to_curves":
+ km.items.remove(kmi)
+ else:
+ continue
+
+
+if __name__ == "__main__":
+ register()
+
diff --git a/modules/add_utils.py b/modules/add_utils.py
new file mode 100644
index 00000000..cb742892
--- /dev/null
+++ b/modules/add_utils.py
@@ -0,0 +1,141 @@
+# ##### 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 #####
+########################################################
+#
+# Before changing this file please discuss with admins.
+#
+########################################################
+# <pep8 compliant>
+
+import bpy
+import mathutils
+from bpy.props import FloatVectorProperty
+
+
+class AddObjectHelper:
+ '''Helper Class for Add Object Operators'''
+ location = FloatVectorProperty(name='Location', description='Location of new Object')
+ rotation = FloatVectorProperty(name='Rotation', description='Rotation of new Object')
+
+
+#Initialize loc, rot of operator
+def add_object_align_init(context, operator):
+ '''Initialize loc, rot of operator
+ context: Blender Context
+ operator: the active Operator (self)
+ Initializes the Operators location and rotation variables
+ according to user preferences (align to view)
+ See AddObjectHelper class
+ Returns Matrix
+ '''
+ if (operator
+ and operator.properties.is_property_set("location")
+ and operator.properties.is_property_set("rotation")):
+ location = mathutils.Matrix.Translation(mathutils.Vector(operator.properties.location))
+ rotation = mathutils.Euler(operator.properties.rotation).to_matrix().resize4x4()
+ else:
+ # TODO, local view cursor!
+ location = mathutils.Matrix.Translation(context.scene.cursor_location)
+
+ if context.user_preferences.edit.object_align == 'VIEW' and context.space_data.type == 'VIEW_3D':
+ rotation = context.space_data.region_3d.view_matrix.rotation_part().invert().resize4x4()
+ else:
+ rotation = mathutils.Matrix()
+
+ # set the operator properties
+ if operator:
+ operator.properties.location = location.translation_part()
+ operator.properties.rotation = rotation.to_euler()
+
+ return location * rotation
+
+
+def add_object_data(context, obdata, operator=None):
+ '''Create Object from data
+
+ context: Blender Context
+ obdata: Object data (mesh, curve, camera,...)
+ operator: the active operator (self)
+
+ Returns the Object
+ '''
+
+ scene = context.scene
+
+ # ugh, could be made nicer
+ for ob in scene.objects:
+ ob.select = False
+
+ obdata.update()
+ obj_new = bpy.data.objects.new(obdata.name, obdata)
+
+ base = scene.objects.link(obj_new)
+ base.select = True
+
+ if context.space_data and context.space_data.type == 'VIEW_3D':
+ base.layers_from_view(context.space_data)
+
+ obj_new.matrix_world = add_object_align_init(context, operator)
+
+ obj_act = scene.objects.active
+
+ if obj_act and obj_act.mode == 'EDIT' and obj_act.type == obj_new.type:
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ obj_act.select = True
+ scene.update() # apply location
+ #scene.objects.active = obj_new
+
+ bpy.ops.object.join() # join into the active.
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ else:
+ scene.objects.active = obj_new
+ if context.user_preferences.edit.use_enter_edit_mode:
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ return base
+
+
+def flatten_vector_list(list):
+ '''flatten a list of vetcors to use in foreach_set and the like'''
+ if not list:
+ return None
+
+ result = []
+ for vec in list:
+ result.extend([i for i in vec])
+
+ return result
+
+
+def list_to_vector_list(list, dimension=3):
+ '''make Vector objects out of a list'''
+ #test if list contains right number of elements
+
+ result = []
+ for i in range(0, len(list), dimension):
+ try:
+ vec = mathutils.Vector([list[i + ind] for ind in range(dimension)])
+ except:
+ print('Number of elemnts doesnt match into the vectors.')
+ return None
+
+ result.append(vec)
+
+ return result
diff --git a/modules/extensions_framework/__init__.py b/modules/extensions_framework/__init__.py
new file mode 100644
index 00000000..08bd57e7
--- /dev/null
+++ b/modules/extensions_framework/__init__.py
@@ -0,0 +1,193 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+import time
+
+import bpy
+
+from extensions_framework.ui import EF_OT_msg
+bpy.types.register(EF_OT_msg)
+del EF_OT_msg
+
+def log(str, popup=False, module_name='EF'):
+ """Print a message to the console, prefixed with the module_name
+ and the current time. If the popup flag is True, the message will
+ be raised in the UI as a warning using the operator bpy.ops.ef.msg.
+
+ """
+ print("[%s %s] %s" %
+ (module_name, time.strftime('%Y-%b-%d %H:%M:%S'), str))
+ if popup:
+ bpy.ops.ef.msg(
+ msg_type='WARNING',
+ msg_text=str
+ )
+
+
+added_property_cache = {}
+
+def init_properties(obj, props, cache=True):
+ """Initialise custom properties in the given object or type.
+ The props list is described in the declarative_property_group
+ class definition. If the cache flag is False, this function
+ will attempt to redefine properties even if they have already been
+ added.
+
+ """
+
+ if not obj in added_property_cache.keys():
+ added_property_cache[obj] = []
+
+ for prop in props:
+ try:
+ if cache and prop['attr'] in added_property_cache[obj]:
+ continue
+
+ if prop['type'] == 'bool':
+ t = bpy.props.BoolProperty
+ a = {k: v for k,v in prop.items() if k in ['name',
+ 'description','default']}
+ elif prop['type'] == 'collection':
+ t = bpy.props.CollectionProperty
+ a = {k: v for k,v in prop.items() if k in ["ptype", "name",
+ "description"]}
+ a['type'] = a['ptype']
+ del a['ptype']
+ elif prop['type'] == 'enum':
+ t = bpy.props.EnumProperty
+ a = {k: v for k,v in prop.items() if k in ["items", "name",
+ "description", "default"]}
+ elif prop['type'] == 'float':
+ t = bpy.props.FloatProperty
+ a = {k: v for k,v in prop.items() if k in ["name",
+ "description", "min", "max", "soft_min", "soft_max",
+ "default", "precision"]}
+ elif prop['type'] == 'float_vector':
+ t = bpy.props.FloatVectorProperty
+ a = {k: v for k,v in prop.items() if k in ["name",
+ "description", "min", "max", "soft_min", "soft_max",
+ "default", "precision", "size", "subtype"]}
+ elif prop['type'] == 'int':
+ t = bpy.props.IntProperty
+ a = {k: v for k,v in prop.items() if k in ["name",
+ "description", "min", "max", "soft_min", "soft_max",
+ "default"]}
+ elif prop['type'] == 'pointer':
+ t = bpy.props.PointerProperty
+ a = {k: v for k,v in prop.items() if k in ["ptype", "name",
+ "description"]}
+ a['type'] = a['ptype']
+ del a['ptype']
+ elif prop['type'] == 'string':
+ t = bpy.props.StringProperty
+ a = {k: v for k,v in prop.items() if k in ["name",
+ "description", "maxlen", "default", "subtype"]}
+ else:
+ continue
+
+ setattr(obj, prop['attr'], t(**a))
+
+ added_property_cache[obj].append(prop['attr'])
+ except KeyError:
+ # Silently skip invalid entries in props
+ continue
+
+
+class declarative_property_group(bpy.types.IDPropertyGroup):
+ """A declarative_property_group describes a set of logically
+ related properties, using a declarative style to list each
+ property type, name, values, and other relevant information.
+ The information provided for each property depends on the
+ property's type.
+
+ The properties list attribute in this class describes the
+ properties present in this group.
+
+ Some additional information about the properties in this group
+ can be specified, so that a UI can be generated to display them.
+ To that end, the controls list attribute and the visibility dict
+ attribute are present here, to be read and interpreted by a
+ property_group_renderer object.
+ See extensions_framework.ui.property_group_renderer.
+
+ """
+
+ """This list controls the order of property layout when rendered
+ by a property_group_renderer. This can be a nested list, where each
+ list becomes a row in the panel layout. Nesting may be to any depth.
+
+ """
+ controls = []
+
+ """The visibility dict controls the display of properties based on
+ the value of other properties. See extensions_framework.validate
+ for test syntax.
+
+ """
+ visibility = {}
+
+ """The properties list describes each property to be created. Each
+ item should be a dict of args to pass to a
+ bpy.props.<?>Property function, with the exception of 'type'
+ which is used and stripped by extensions_framework in order to
+ determine which Property creation function to call.
+
+ Example item:
+ {
+ 'type': 'int', # bpy.props.IntProperty
+ 'attr': 'threads', # bpy.types.<type>.threads
+ 'name': 'Render Threads', # Rendered next to the UI
+ 'description': 'Number of threads to use', # Tooltip text in the UI
+ 'default': 1,
+ 'min': 1,
+ 'soft_min': 1,
+ 'max': 64,
+ 'soft_max': 64
+ }
+
+ """
+ properties = []
+
+ def draw_callback(self, context):
+ """Sub-classes can override this to get a callback when
+ rendering is completed by a property_group_renderer sub-class.
+
+ """
+
+ pass
+
+ @classmethod
+ def get_exportable_properties(cls):
+ """Return a list of properties which have the 'save_in_preset' key
+ set to True, and hence should be saved into preset files.
+
+ """
+
+ out = []
+ for prop in cls.properties:
+ if 'save_in_preset' in prop.keys() and prop['save_in_preset']:
+ out.append(prop)
+ return out
diff --git a/modules/extensions_framework/engine.py b/modules/extensions_framework/engine.py
new file mode 100644
index 00000000..0a6fecb0
--- /dev/null
+++ b/modules/extensions_framework/engine.py
@@ -0,0 +1,39 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+from extensions_framework.plugin import plugin
+
+class engine_base(plugin):
+ """Render Engine plugin base class
+
+ TODO: Remove, this class hasn't grown to be useful
+
+ """
+
+ bl_label = 'Abstract Render Engine Base Class'
+
+ def render(self, scene):
+ pass
diff --git a/modules/extensions_framework/outputs/__init__.py b/modules/extensions_framework/outputs/__init__.py
new file mode 100644
index 00000000..f05ed25f
--- /dev/null
+++ b/modules/extensions_framework/outputs/__init__.py
@@ -0,0 +1,26 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
diff --git a/modules/extensions_framework/outputs/xml_output.py b/modules/extensions_framework/outputs/xml_output.py
new file mode 100644
index 00000000..3b1102c1
--- /dev/null
+++ b/modules/extensions_framework/outputs/xml_output.py
@@ -0,0 +1,116 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+import xml.etree.cElementTree as ET
+import xml.dom.minidom as MD
+
+class xml_output(object):
+ """This class serves to describe an XML output, it uses
+ cElementTree and minidom to construct and format the XML
+ data.
+
+ """
+
+ """The format dict describes the XML structure that this class
+ should generate, and which properties should be used to fill
+ the XML data structure
+
+ """
+ format = {}
+
+ def __str__(self):
+ return ET.tostring(self.root)
+
+ def write_pretty(self, file):
+ """Write a formatted XML string to file"""
+ xml_dom = MD.parseString(ET.tostring(self.root, encoding='utf-8'))
+ xml_dom.writexml(file, addindent=' ', newl='\n', encoding='utf-8')
+
+ def pretty(self):
+ """Return a formatted XML string"""
+ xml_str = MD.parseString(ET.tostring(self.root))
+ return xml_str.toprettyxml()
+
+ def get_format(self):
+ """This should be overridden in classes that produce XML
+ conditionally
+
+ """
+ return self.format
+
+ def compute(self, context):
+ """Compute the XML output from the input format"""
+ self.context = context
+
+ self.root = ET.Element(self.root_element)
+ self.parse_dict(self.get_format(), self.root)
+
+ return self.root
+
+ """Formatting functions for various data types"""
+ format_types = {
+ 'bool': lambda c,x: str(x).lower(),
+ 'collection': lambda c,x: x,
+ 'enum': lambda c,x: x,
+ 'float': lambda c,x: x,
+ 'int': lambda c,x: x,
+ 'pointer': lambda c,x: x,
+ 'string': lambda c,x: x,
+ }
+
+ def parse_dict(self, d, elem):
+ """Parse the values in the format dict and collect the
+ formatted data into XML structure starting at self.root
+
+ """
+ for key in d.keys():
+ # tuple provides multiple child elements
+ if type(d[key]) is tuple:
+ for cd in d[key]:
+ self.parse_dict({key:cd}, elem)
+ continue # don't create empty element for tuple child
+
+ x = ET.SubElement(elem, key)
+
+ # dictionary provides nested elements
+ if type(d[key]) is dict:
+ self.parse_dict(d[key], x)
+
+ # list provides direct value insertion
+ elif type(d[key]) is list:
+ x.text = ' '.join([str(i) for i in d[key]])
+
+ # else look up property
+ else:
+ for p in self.properties:
+ if d[key] == p['attr']:
+ if 'compute' in p.keys():
+ x.text = str(p['compute'](self.context, self))
+ else:
+ x.text = str(
+ self.format_types[p['type']](self.context,
+ getattr(self, d[key]))
+ )
diff --git a/modules/extensions_framework/plugin.py b/modules/extensions_framework/plugin.py
new file mode 100644
index 00000000..76f41930
--- /dev/null
+++ b/modules/extensions_framework/plugin.py
@@ -0,0 +1,94 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+import bpy
+
+from extensions_framework import init_properties
+from extensions_framework import log
+
+class plugin(object):
+ """Base class for plugins which wish to make use of utilities
+ provided in extensions_framework. Using the property_groups
+ attribute and the install() and uninstall() methods, a large number
+ of custom scene properties can be easily defined, displayed and
+ managed.
+
+ TODO: Rename, 'extension' would be more appropriate than 'plugin'
+
+ """
+
+ """The property_groups defines a list of declarative_property_group
+ types to create in specified types during the initialisation of the
+ plugin.
+ Item format:
+ ('bpy.type prototype to attach to', <declarative_property_group>)
+
+ Example item:
+ ('Scene', myaddon_property_group)
+ In this example, a new property group will be attached to
+ bpy.types.Scene and all of the properties described in that group
+ will be added to it.
+ See extensions_framework.declarative_property_group.
+
+ """
+ property_groups = []
+
+ @classmethod
+ def install(r_class):
+ """Initialise this plugin. So far, all this does is to create
+ custom property groups specified in the property_groups
+ attribute.
+
+ """
+ for property_group_parent, property_group in r_class.property_groups:
+ call_init = False
+ if property_group_parent is not None:
+ prototype = getattr(bpy.types, property_group_parent)
+ if not hasattr(prototype, property_group.__name__):
+ init_properties(prototype, [{
+ 'type': 'pointer',
+ 'attr': property_group.__name__,
+ 'ptype': property_group,
+ 'name': property_group.__name__,
+ 'description': property_group.__name__
+ }])
+ call_init = True
+ else:
+ call_init = True
+
+ if call_init:
+ init_properties(property_group, property_group.properties)
+
+ log('Extension "%s" initialised' % r_class.bl_label)
+
+ @classmethod
+ def uninstall(r_class):
+ """Unregister property groups in reverse order"""
+ reverse_property_groups = [p for p in r_class.property_groups]
+ reverse_property_groups.reverse()
+ for property_group_parent, property_group in reverse_property_groups:
+ prototype = getattr(bpy.types, property_group_parent)
+ prototype.RemoveProperty(property_group.__name__)
diff --git a/modules/extensions_framework/ui.py b/modules/extensions_framework/ui.py
new file mode 100644
index 00000000..5bbd69c0
--- /dev/null
+++ b/modules/extensions_framework/ui.py
@@ -0,0 +1,253 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+import bpy
+
+from extensions_framework.validate import Visibility
+
+class EF_OT_msg(bpy.types.Operator):
+ """An operator to show simple messages in the UI"""
+ bl_idname = 'ef.msg'
+ bl_label = 'Show UI Message'
+ msg_type = bpy.props.StringProperty(default='INFO')
+ msg_text = bpy.props.StringProperty(default='')
+ def execute(self, context):
+ self.report({self.properties.msg_type}, self.properties.msg_text)
+ return {'FINISHED'}
+
+def _get_item_from_context(context, path):
+ """Utility to get an object when the path to it is known:
+ _get_item_from_context(context, ['a','b','c']) returns
+ context.a.b.c
+ No error checking is performed other than checking that context
+ is not None. Exceptions caused by invalid path should be caught in
+ the calling code.
+
+ """
+
+ if context is not None:
+ for p in path:
+ context = getattr(context, p)
+ return context
+
+class property_group_renderer(object):
+ """Mix-in class for sub-classes of bpy.types.Panel. This class
+ will provide the draw() method which implements drawing one or
+ more property groups derived from
+ extensions_framework.declarative_propery_group.
+ The display_property_groups list attribute describes which
+ declarative_property_groups should be drawn in the Panel, and
+ how to extract those groups from the context passed to draw().
+
+ """
+
+ """The display_property_groups list attribute specifies which
+ custom declarative_property_groups this panel should draw, and
+ where to find that property group in the active context.
+ Example item:
+ ( ('scene',), 'myaddon_property_group')
+ In this case, this renderer will look for properties in
+ context.scene.myaddon_property_group to draw in the Panel.
+
+ """
+ display_property_groups = []
+
+ def draw(self, context):
+ """Sub-classes should override this if they need to display
+ other (object-related) property groups. super().draw(context)
+ can be a useful call in those cases.
+
+ """
+ for property_group_path, property_group_name in \
+ self.display_property_groups:
+ ctx = _get_item_from_context(context, property_group_path)
+ property_group = getattr(ctx, property_group_name)
+ for p in property_group.controls:
+ self.draw_column(p, self.layout, ctx, context,
+ property_group=property_group)
+ property_group.draw_callback(context)
+
+ def check_visibility(self, lookup_property, property_group):
+ """Determine if the lookup_property should be drawn in the Panel"""
+ vt = Visibility(property_group)
+ if lookup_property in property_group.visibility.keys():
+ if hasattr(property_group, lookup_property):
+ member = getattr(property_group, lookup_property)
+ else:
+ member = None
+ return vt.test_logic(member,
+ property_group.visibility[lookup_property])
+ else:
+ return True
+
+ # tab_level = 0
+
+ def is_real_property(self, lookup_property, property_group):
+ for prop in property_group.properties:
+ if prop['attr'] == lookup_property:
+ return prop['type'] not in ['text', 'prop_search']
+
+ return False
+
+ def draw_column(self, control_list_item, layout, context,
+ supercontext=None, property_group=None):
+ # self.tab_level += 1
+ """Draw a column's worth of UI controls in this Panel"""
+ if type(control_list_item) is list:
+ draw_row = False
+
+ found_percent = None
+ # print('\t'*self.tab_level, '--', property_group, '--')
+ for sp in control_list_item:
+ # print('\t'*self.tab_level, sp)
+ if type(sp) is float:
+ found_percent = sp
+ elif type(sp) is list:
+ for ssp in [s for s in sp if self.is_real_property(s, property_group)]:
+ draw_row = draw_row or self.check_visibility(ssp,
+ property_group)
+ # print('\t'*self.tab_level, 'List: ', draw_row)
+ else:
+ draw_row = draw_row or self.check_visibility(sp,
+ property_group)
+ # print('\t'*self.tab_level, 'Single: ', draw_row)
+ # print('\t'*self.tab_level, '-->', draw_row)
+ # print('\t'*self.tab_level, '--', property_group, '--')
+
+ next_items = [s for s in control_list_item if type(s) in [str, list]]
+ if draw_row and len(next_items) > 0:
+ if found_percent is not None:
+ splt = layout.split(percentage=found_percent)
+ else:
+ splt = layout.row(True)
+ for sp in next_items:
+ col2 = splt.column()
+ self.draw_column(sp, col2, context, supercontext,
+ property_group)
+ else:
+ if self.check_visibility(control_list_item, property_group):
+
+ for current_property in property_group.properties:
+ if current_property['attr'] == control_list_item:
+ current_property_keys = current_property.keys()
+ if 'type' in current_property_keys:
+
+ if current_property['type'] in ['int', 'float',
+ 'float_vector', 'enum', 'string']:
+ layout.prop(
+ property_group,
+ control_list_item,
+ text = current_property['name'],
+ expand = current_property['expand'] \
+ if 'expand' in current_property_keys \
+ else False,
+ slider = current_property['slider'] \
+ if 'slider' in current_property_keys \
+ else False,
+ toggle = current_property['toggle'] \
+ if 'toggle' in current_property_keys \
+ else False,
+ icon_only = current_property['icon_only'] \
+ if 'icon_only' in current_property_keys \
+ else False,
+ event = current_property['event'] \
+ if 'event' in current_property_keys \
+ else False,
+ full_event = current_property['full_event'] \
+ if 'full_event' in current_property_keys \
+ else False,
+ emboss = current_property['emboss'] \
+ if 'emboss' in current_property_keys \
+ else True,
+ )
+ if current_property['type'] in ['bool']:
+ layout.prop(
+ property_group,
+ control_list_item,
+ text = current_property['name'],
+ toggle = current_property['toggle'] \
+ if 'toggle' in current_property_keys \
+ else False,
+ icon_only = current_property['icon_only'] \
+ if 'icon_only' in current_property_keys \
+ else False,
+ event = current_property['event'] \
+ if 'event' in current_property_keys \
+ else False,
+ full_event = current_property['full_event'] \
+ if 'full_event' in current_property_keys \
+ else False,
+ emboss = current_property['emboss'] \
+ if 'emboss' in current_property_keys \
+ else True,
+ )
+ elif current_property['type'] in ['operator']:
+ layout.operator(current_property['operator'],
+ text = current_property['text'],
+ icon = current_property['icon']
+ )
+
+ elif current_property['type'] in ['text']:
+ layout.label(
+ text = current_property['name']
+ )
+
+ elif current_property['type'] in ['template_list']:
+ layout.template_list(
+ current_property['src'](supercontext, context),
+ current_property['src_attr'],
+ current_property['trg'](supercontext, context),
+ current_property['trg_attr'],
+ rows = 4 \
+ if not 'rows' in current_property_keys \
+ else current_property['rows'],
+ maxrows = 4 \
+ if not 'rows' in current_property_keys \
+ else current_property['rows'],
+ type = 'DEFAULT' \
+ if not 'list_type' in current_property_keys \
+ else current_property['list_type']
+ )
+
+ elif current_property['type'] in ['prop_search']:
+ layout.prop_search(
+ current_property['trg'](supercontext,
+ context),
+ current_property['trg_attr'],
+ current_property['src'](supercontext,
+ context),
+ current_property['src_attr'],
+ text = current_property['name'],
+ )
+ else:
+ layout.prop(property_group, control_list_item)
+
+ # Fire a draw callback if specified
+ if 'draw' in current_property_keys:
+ current_property['draw'](supercontext, context)
+
+ break
+ # self.tab_level -= 1
diff --git a/modules/extensions_framework/util.py b/modules/extensions_framework/util.py
new file mode 100644
index 00000000..b210e729
--- /dev/null
+++ b/modules/extensions_framework/util.py
@@ -0,0 +1,225 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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 *****
+#
+import configparser
+import datetime
+import os
+import tempfile
+import threading
+
+import bpy
+
+"""List of possibly appropriate paths to load/save addon config from/to"""
+config_paths = []
+if bpy.utils.user_resource('CONFIG', '') != "": config_paths.append(bpy.utils.user_resource('CONFIG', '', create=True))
+if bpy.utils.user_resource('SCRIPTS', '') != "": config_paths.append(bpy.utils.user_resource('SCRIPTS', '', create=True))
+# want to scan other script paths in reverse order, since the user path comes last
+sp = [p for p in bpy.utils.script_paths() if p != '']
+sp.reverse()
+config_paths.extend(sp)
+
+"""This path is set at the start of export, so that calls to
+path_relative_to_export() can make all exported paths relative to
+this one.
+"""
+export_path = '';
+
+def path_relative_to_export(p):
+ """Return a path that is relative to the export path"""
+ global export_path
+ p = filesystem_path(p)
+ try:
+ relp = os.path.relpath(p, os.path.dirname(export_path))
+ except ValueError: # path on different drive on windows
+ relp = p
+
+ return relp.replace('\\', '/')
+
+def filesystem_path(p):
+ """Resolve a relative Blender path to a real filesystem path"""
+ if p.startswith('//'):
+ pout = bpy.path.abspath(p)
+ else:
+ pout = os.path.realpath(p)
+
+ return pout.replace('\\', '/')
+
+# TODO: - somehow specify TYPES to get/set from config
+
+def find_config_value(module, section, key, default):
+ """Attempt to find the configuration value specified by string key
+ in the specified section of module's configuration file. If it is
+ not found, return default.
+
+ """
+ global config_paths
+ fc = []
+ for p in config_paths:
+ if os.path.exists(p) and os.path.isdir(p) and os.access(p, os.W_OK):
+ fc.append( '/'.join([p, '%s.cfg' % module]))
+
+ if len(fc) < 1:
+ print('Cannot find %s config file path' % module)
+ return default
+
+ cp = configparser.SafeConfigParser()
+
+ cfg_files = cp.read(fc)
+ if len(cfg_files) > 0:
+ try:
+ val = cp.get(section, key)
+ if val == 'true':
+ return True
+ elif val == 'false':
+ return False
+ else:
+ return val
+ except:
+ return default
+ else:
+ return default
+
+def write_config_value(module, section, key, value):
+ """Attempt to write the configuration value specified by string key
+ in the specified section of module's configuration file.
+
+ """
+ global config_paths
+ fc = []
+ for p in config_paths:
+ if os.path.exists(p) and os.path.isdir(p) and os.access(p, os.W_OK):
+ fc.append( '/'.join([p, '%s.cfg' % module]))
+
+ if len(fc) < 1:
+ raise Exception('Cannot find a writable path to store %s config file' %
+ module)
+
+ cp = configparser.SafeConfigParser()
+
+ cfg_files = cp.read(fc)
+
+ if not cp.has_section(section):
+ cp.add_section(section)
+
+ if value == True:
+ cp.set(section, key, 'true')
+ elif value == False:
+ cp.set(section, key, 'false')
+ else:
+ cp.set(section, key, value)
+
+ if len(cfg_files) < 1:
+ cfg_files = fc
+
+ fh=open(cfg_files[0],'w')
+ cp.write(fh)
+ fh.close()
+
+ return True
+
+def scene_filename():
+ """Construct a safe scene filename, using 'untitled' instead of ''"""
+ filename = os.path.splitext(os.path.basename(bpy.data.filepath))[0]
+ if filename == '':
+ filename = 'untitled'
+ return bpy.path.clean_name(filename)
+
+def temp_directory():
+ """Return the system temp directory"""
+ return tempfile.gettempdir()
+
+def temp_file(ext='tmp'):
+ """Get a temporary filename with the given extension. This function
+ will actually attempt to create the file."""
+ tf, fn = tempfile.mkstemp(suffix='.%s'%ext)
+ os.close(tf)
+ return fn
+
+class TimerThread(threading.Thread):
+ """Periodically call self.kick(). The period of time in seconds
+ between calling is given by self.KICK_PERIOD, and the first call
+ may be delayed by setting self.STARTUP_DELAY, also in seconds.
+ self.kick() will continue to be called at regular intervals until
+ self.stop() is called. Since this is a thread, calling self.join()
+ may be wise after calling self.stop() if self.kick() is performing
+ a task necessary for the continuation of the program.
+ The object that creates this TimerThread may pass into it data
+ needed during self.kick() as a dict LocalStorage in __init__().
+
+ """
+ STARTUP_DELAY = 0
+ KICK_PERIOD = 8
+
+ active = True
+ timer = None
+
+ LocalStorage = None
+
+ def __init__(self, LocalStorage=dict()):
+ threading.Thread.__init__(self)
+ self.LocalStorage = LocalStorage
+
+ def set_kick_period(self, period):
+ """Adjust the KICK_PERIOD between __init__() and start()"""
+ self.KICK_PERIOD = period + self.STARTUP_DELAY
+
+ def stop(self):
+ """Stop this timer. This method does not join()"""
+ self.active = False
+ if self.timer is not None:
+ self.timer.cancel()
+
+ def run(self):
+ """Timed Thread loop"""
+ while self.active:
+ self.timer = threading.Timer(self.KICK_PERIOD, self.kick_caller)
+ self.timer.start()
+ if self.timer.isAlive(): self.timer.join()
+
+ def kick_caller(self):
+ """Intermediary between the kick-wait-loop and kick to allow
+ adjustment of the first KICK_PERIOD by STARTUP_DELAY
+
+ """
+ if self.STARTUP_DELAY > 0:
+ self.KICK_PERIOD -= self.STARTUP_DELAY
+ self.STARTUP_DELAY = 0
+
+ self.kick()
+
+ def kick(self):
+ """Sub-classes do their work here"""
+ pass
+
+def format_elapsed_time(t):
+ """Format a duration in seconds as an HH:MM:SS format time"""
+
+ td = datetime.timedelta(seconds=t)
+ min = td.days*1440 + td.seconds/60.0
+ hrs = td.days*24 + td.seconds/3600.0
+
+ return '%i:%02i:%02i' % (hrs, min%60, td.seconds%60)
diff --git a/modules/extensions_framework/validate.py b/modules/extensions_framework/validate.py
new file mode 100644
index 00000000..d9cee8fd
--- /dev/null
+++ b/modules/extensions_framework/validate.py
@@ -0,0 +1,213 @@
+# -*- coding: utf8 -*-
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# --------------------------------------------------------------------------
+# Blender 2.5 Extensions Framework
+# --------------------------------------------------------------------------
+#
+# Authors:
+# Doug Hammond
+#
+# 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, see <http://www.gnu.org/licenses/>.
+#
+# ***** END GPL LICENCE BLOCK *****
+#
+"""
+Pure logic and validation class.
+
+By using a Subject object, and a dict of described logic tests, it
+is possible to arrive at a True or False result for various purposes:
+1. Data validation
+2. UI control visibility
+
+A Subject can be any object whose members are readable with getattr() :
+class Subject(object):
+ a = 0
+ b = 1
+ c = 'foo'
+ d = True
+ e = False
+ f = 8
+ g = 'bar'
+
+
+Tests are described thus:
+
+Use the special list types Logic_AND and Logic_OR to describe
+combinations of values and other members. Use Logic_Operator for
+numerical comparison.
+
+With regards to Subject, each of these evaluate to True:
+TESTA = {
+ 'a': 0,
+ 'c': Logic_OR([ 'foo', 'bar' ]),
+ 'd': Logic_AND([True, True]),
+ 'f': Logic_AND([8, {'b': 1}]),
+ 'e': {'b': Logic_Operator({'gte':1, 'lt':3}) },
+ 'g': Logic_OR([ 'baz', Logic_AND([{'b': 1}, {'f': 8}]) ])
+}
+
+With regards to Subject, each of these evaluate to False:
+TESTB = {
+ 'a': 'foo',
+ 'c': Logic_OR([ 'bar', 'baz' ]),
+ 'd': Logic_AND([ True, 'foo' ]),
+ 'f': Logic_AND([9, {'b': 1}]),
+ 'e': {'b': Logic_Operator({'gte':-10, 'lt': 1}) },
+ 'g': Logic_OR([ 'baz', Logic_AND([{'b':0}, {'f': 8}]) ])
+}
+
+With regards to Subject, this test is invalid
+TESTC = {
+ 'n': 0
+}
+
+Tests are executed thus:
+S = Subject()
+L = Logician(S)
+L.execute(TESTA)
+
+"""
+
+class Logic_AND(list):
+ pass
+class Logic_OR(list):
+ pass
+class Logic_Operator(dict):
+ pass
+
+class Logician(object):
+ """Given a subject and a dict that describes tests to perform on
+ its members, this class will evaluate True or False results for
+ each member/test pair. See the examples below for test syntax.
+
+ """
+
+ subject = None
+ def __init__(self, subject):
+ self.subject = subject
+
+ def get_member(self, member_name):
+ """Get a member value from the subject object. Raise exception
+ if subject is None or member not found.
+
+ """
+ if self.subject is None:
+ raise Exception('Cannot run tests on a subject which is None')
+
+ return getattr(self.subject, member_name)
+
+ def test_logic(self, member, logic, operator='eq'):
+ """Find the type of test to run on member, and perform that test"""
+
+ if type(logic) is dict:
+ return self.test_dict(member, logic)
+ elif type(logic) is Logic_AND:
+ return self.test_and(member, logic)
+ elif type(logic) is Logic_OR:
+ return self.test_or(member, logic)
+ elif type(logic) is Logic_Operator:
+ return self.test_operator(member, logic)
+ else:
+ # compare the value, I think using Logic_Operator() here
+ # allows completeness in test_operator(), but I can't put
+ # my finger on why for the minute
+ return self.test_operator(member,
+ Logic_Operator({operator: logic}))
+
+ def test_operator(self, member, value):
+ """Execute the operators contained within value and expect that
+ ALL operators are True
+
+ """
+
+ # something in this method is incomplete, what if operand is
+ # a dict, Logic_AND, Logic_OR or another Logic_Operator ?
+ # Do those constructs even make any sense ?
+
+ result = True
+ for operator, operand in value.items():
+ operator = operator.lower().strip()
+ if operator in ['eq', '==']:
+ result &= member==operand
+ if operator in ['not', '!=']:
+ result &= member!=operand
+ if operator in ['lt', '<']:
+ result &= member<operand
+ if operator in ['lte', '<=']:
+ result &= member<=operand
+ if operator in ['gt', '>']:
+ result &= member>operand
+ if operator in ['gte', '>=']:
+ result &= member>=operand
+ if operator in ['and', '&']:
+ result &= member&operand
+ if operator in ['or', '|']:
+ result &= member|operand
+ if operator in ['len']:
+ result &= len(member)==operand
+ # I can think of some more, but they're probably not useful.
+
+ return result
+
+ def test_or(self, member, logic):
+ """Member is a value, logic is a set of values, ANY of which
+ can be True
+
+ """
+ result = False
+ for test in logic:
+ result |= self.test_logic(member, test)
+
+ return result
+
+ def test_and(self, member, logic):
+ """Member is a value, logic is a list of values, ALL of which
+ must be True
+
+ """
+ result = True
+ for test in logic:
+ result &= self.test_logic(member, test)
+
+ return result
+
+ def test_dict(self, member, logic):
+ """Member is a value, logic is a dict of other members to
+ compare to. All other member tests must be True
+
+ """
+ result = True
+ for other_member, test in logic.items():
+ result &= self.test_logic(self.get_member(other_member), test)
+
+ return result
+
+ def execute(self, test):
+ """Subject is an object, test is a dict of {member: test} pairs
+ to perform on subject's members. Wach key in test is a member
+ of subject.
+
+ """
+
+ for member_name, logic in test.items():
+ result = self.test_logic(self.get_member(member_name), logic)
+ print('member %s is %s' % (member_name, result))
+
+# A couple of name aliases
+class Validation(Logician):
+ pass
+class Visibility(Logician):
+ pass
diff --git a/object_add_chain.py b/object_add_chain.py
new file mode 100644
index 00000000..9f3bfbd7
--- /dev/null
+++ b/object_add_chain.py
@@ -0,0 +1,150 @@
+# ##### 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 Chain",
+ "author": "Brian Hinton (Nichod)",
+ "version": (0,1),
+ "blender": (2, 5, 3),
+ "api": 31965,
+ "location": "View3D > Add > Mesh > Chain",
+ "description": "Adds Chain with curve guide for easy creation",
+ "warning": "",
+ "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), layers=(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), layers=(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_edit_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.use_object_offset = True
+ array.relative_offset_displace = [ 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
+def menu_func(self, context):
+ self.layout.operator(AddChain.bl_idname, text="Chain", icon='PLUGIN')
+
+
+def register():
+ # Add "Chain" menu to the "Add Mesh" menu.
+ bpy.types.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ # 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_animrenderbake.py b/object_animrenderbake.py
new file mode 100644
index 00000000..02e85eee
--- /dev/null
+++ b/object_animrenderbake.py
@@ -0,0 +1,184 @@
+# ##### 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": "Animated Render Baker",
+ "author": "Janne Karhu (jahka)",
+ "version": (1, 0),
+ "blender": (2, 5, 5),
+ "location": "Render Properties > Bake",
+ "description": "Renderbakes a series of frames",
+ "category": "Object",
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/Object/Animated_Render_Baker',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=24836&group_id=153&atid=467'}
+
+import bpy
+from bpy.props import *
+
+class OBJECT_OT_animrenderbake(bpy.types.Operator):
+ bl_label = "Animated Render Bake"
+ bl_description= "Bake animated image textures of selected objects"
+ bl_idname = "object.anim_bake_image"
+ bl_register = True
+
+ def framefile(self, orig, frame):
+ '''
+ Set frame number to file name image.png -> image0013.png
+ '''
+ dot = orig.rfind(".")
+ return orig[:dot] + ('%04d' % frame) + orig[dot:]
+
+ def invoke(self, context, event):
+ import bpy
+ import shutil
+
+ scene = context.scene
+
+ start = scene.animrenderbake_start
+ end = scene.animrenderbake_end
+
+ # Check for errors before starting
+ if start >= end:
+ self.report({'ERROR'}, "Start frame must be smaller than end frame")
+ return {'CANCELLED'}
+
+ selected = context.selected_objects
+
+ # Only single object baking for now
+ if scene.render.use_bake_selected_to_active:
+ if len(selected) > 2:
+ self.report({'ERROR'}, "Select only two objects for animated baking")
+ return {'CANCELLED'}
+ elif len(selected) > 1:
+ self.report({'ERROR'}, "Select only one object for animated baking")
+ return {'CANCELLED'}
+
+ if context.active_object.type != 'MESH':
+ self.report({'ERROR'}, "The baked object must be a mesh object")
+ return {'CANCELLED'}
+
+ img = None
+
+ #find the image that's used for rendering
+ for uvtex in context.active_object.data.uv_textures:
+ if uvtex.active_render == True:
+ for uvdata in uvtex.data:
+ if uvdata.image != None:
+ img = uvdata.image
+ break
+
+ if img == None:
+ self.report({'ERROR'}, "No valid image found to bake to")
+ return {'CANCELLED'}
+
+ if img.is_dirty:
+ self.report({'ERROR'}, "Save the image that's used for baking before use")
+ return {'CANCELLED'}
+
+ # make sure we have an absolute path so that copying works for sure
+ absp = bpy.path.abspath(img.filepath)
+
+ print("Animated baking for frames " + str(start) + " - " + str(end))
+
+ for cfra in range(start, end+1):
+ print("Baking frame " + str(cfra))
+
+ # update scene to new frame and bake to template image
+ scene.frame_set(cfra)
+ ret = bpy.ops.object.bake_image()
+ if 'CANCELLED' in ret:
+ return {'CANCELLED'}
+
+ #currently the api doesn't allow img.save_as(), so just save the template image as usual for every frame and copy to a file with frame specific filename
+ img.save()
+ shutil.copyfile(absp, self.framefile(absp, cfra))
+
+ print("Saved " + self.framefile(absp, cfra))
+ print("Baking done!")
+
+ return{'FINISHED'}
+
+# modifier copy of original bake panel draw function
+def draw_animrenderbake(self, context):
+ layout = self.layout
+
+ rd = context.scene.render
+
+ row = layout.row()
+ row.operator("object.bake_image", icon='RENDER_STILL')
+
+ #----------- beginning of modifications ----------------
+ row.operator("object.anim_bake_image", text="Animated Bake", icon="RENDER_ANIMATION")
+ row = layout.row(align=True)
+ row.prop(context.scene, "animrenderbake_start")
+ row.prop(context.scene, "animrenderbake_end")
+ #-------------- end of modifications ---------------------
+
+ layout.prop(rd, "bake_type")
+
+ if rd.bake_type == 'NORMALS':
+ layout.prop(rd, "bake_normal_space")
+ elif rd.bake_type in ('DISPLACEMENT', 'AO'):
+ layout.prop(rd, "use_bake_normalize")
+
+ # col.prop(rd, "bake_aa_mode")
+ # col.prop(rd, "use_bake_antialiasing")
+
+ layout.separator()
+
+ split = layout.split()
+
+ col = split.column()
+ col.prop(rd, "use_bake_clear")
+ col.prop(rd, "bake_margin")
+ col.prop(rd, "bake_quad_split", text="Split")
+
+ col = split.column()
+ col.prop(rd, "use_bake_selected_to_active")
+ sub = col.column()
+ sub.active = rd.use_bake_selected_to_active
+ sub.prop(rd, "bake_distance")
+ sub.prop(rd, "bake_bias")
+
+def register():
+ bpy.types.Scene.animrenderbake_start = IntProperty(
+ name="Start",
+ description="Start frame of the animated bake",
+ default=1)
+
+ bpy.types.Scene.animrenderbake_end = IntProperty(
+ name="End",
+ description="End frame of the animated bake",
+ default=250)
+
+ # replace original panel draw function with modified one
+ panel = bpy.types.RENDER_PT_bake
+ panel.old_draw = panel.draw
+ panel.draw = draw_animrenderbake
+
+def unregister():
+ # restore original panel draw function
+ bpy.types.RENDER_PT_bake.draw = bpy.types.RENDER_PT_bake.old_draw
+ del bpy.types.RENDER_PT_bake.old_draw
+ del bpy.types.Scene.animrenderbake_start
+ del bpy.types.Scene.animrenderbake_end
+
+if __name__ == "__main__":
+ register()
diff --git a/object_cloud_gen.py b/object_cloud_gen.py
new file mode 100644
index 00000000..bd865823
--- /dev/null
+++ b/object_cloud_gen.py
@@ -0,0 +1,751 @@
+# ##### 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": "Cloud Generator",
+ "author": "Nick Keeline(nrk)",
+ "version": (1,0),
+ "blender": (2, 5, 5),
+ "api": 31965,
+ "location": "Tool Shelf ",
+ "description": "Creates Volumetric Clouds",
+ "warning": "",
+ "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"}
+
+"""
+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
+Rev 0.8 fixed particles by commenting out add cloud texture force field
+Rev 0.9 Added smoothing and explosion material
+Rev 1.0 Added ability to convert object with particle system to cloud and auto resizing of bound box
+"""
+
+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 getMeshandPutinEditMode(scene, object):
+
+ # Go into Object Mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Select the object
+ object.select = True
+ scene.objects.active = object
+
+ # Go into Edit Mode
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ return object.data
+
+def maxAndMinVerts(scene, object):
+
+ mesh = getMeshandPutinEditMode(scene, object)
+ verts = mesh.vertices
+
+ #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]
+
+ return [maxVert, minVert]
+
+def makeObjectIntoBoundBox(scene, object, sizeDifference, takeFromObject):
+
+ #Let's find the max and min of the reference object, it can be the same as the destination object
+ [maxVert, minVert] = maxAndMinVerts(scene, takeFromObject)
+
+ #get objects mesh
+ mesh = getMeshandPutinEditMode(scene, object)
+
+ #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):
+ scene.objects.unlink(obj)
+ bpy.data.objects.remove(obj)
+
+
+def makeParent(parentobj, childobj, scene):
+
+ applyScaleRotLoc(scene, parentobj)
+ applyScaleRotLoc(scene, childobj)
+ childobj.parent = parentobj
+
+
+def addNewObject(scene, name, copyobj):
+
+ # 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 getpdensitytexture(object):
+
+ for mslot in object.material_slots:
+ mat = mslot.material
+ for tslot in mat.texture_slots:
+ if tslot!= 'NoneType':
+ tex = tslot.texture
+ if tex.type == 'POINT_DENSITY':
+ if tex.point_density.point_source == 'PARTICLE_SYSTEM':
+ return tex
+
+def removeParticleSystemFromObj(scene, object):
+
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+ # Select the object.
+ object.select = True
+ scene.objects.active = object
+
+ bpy.ops.object.particle_system_remove()
+
+ # Deselect All
+ bpy.ops.object.select_all(action='DESELECT')
+
+def convertParticlesToMesh(scene, particlesobj, destobj, replacemesh):
+
+ # Select the Destination object.
+ destobj.select = True
+ scene.objects.active = destobj
+
+ #Go to Edit Mode
+ bpy.ops.object.mode_set(mode='EDIT',toggle=False)
+
+ #Delete everything in mesh if replace true
+ if replacemesh:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.delete(type='ALL')
+
+ meshPnts = destobj.data
+
+ listCloudParticles = particlesobj.particles
+
+ listMeshPnts = []
+ for pTicle in listCloudParticles:
+ listMeshPnts.append(pTicle.location)
+
+ # Must be in object mode for 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()
+
+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 the action we want to take
+def getActionToDo(obj):
+
+ if not obj or obj.type != 'MESH':
+ return 'NOT_OBJ_DO_NOTHING'
+ elif obj is None:
+ return 'NO_SELECTION_DO_NOTHING'
+ elif "CloudMember" in obj:
+ if obj["CloudMember"] != None:
+ if obj["CloudMember"] == "MainObj":
+ return 'DEGENERATE'
+ elif obj["CloudMember"] == "CreatedObj" and len(obj.particle_systems) > 0:
+ return 'CLOUD_CONVERT_TO_MESH'
+ else:
+ return 'CLOUD_DO_NOTHING'
+ elif obj.type == 'MESH':
+ return 'GENERATE'
+ else:
+ return 'DO_NOTHING'
+
+class VIEW3D_PT_tools_cloud(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+
+ bl_label = "Cloud Generator"
+ bl_context = "objectmode"
+
+ def draw(self, context):
+ active_obj = context.active_object
+ layout = self.layout
+ col = layout.column(align=True)
+
+ WhatToDo = getActionToDo(active_obj)
+
+ if WhatToDo == 'DEGENERATE':
+
+ col.operator("cloud.generate_cloud", text="DeGenerate")
+
+ elif WhatToDo == 'CLOUD_CONVERT_TO_MESH':
+
+ col.operator("cloud.generate_cloud", text="Convert to Mesh")
+
+ elif WhatToDo == 'NO_SELECTION_DO_NOTHING':
+
+ col.label(text="Select one or more")
+ col.label(text="objects to generate")
+ col.label(text="a cloud.")
+
+ elif WhatToDo == 'CLOUD_DO_NOTHING':
+
+ col.label(text="Must select")
+ col.label(text="bound box")
+
+ elif WhatToDo == 'GENERATE':
+
+ col.operator("cloud.generate_cloud", text="Generate Cloud")
+
+ col.prop(context.scene, "cloud_type")
+ col.prop(context.scene, "cloudparticles")
+ col.prop(context.scene, "cloudsmoothing")
+ else:
+ col.label(text="Select one or more")
+ col.label(text="objects to generate")
+ col.label(text="a cloud.")
+
+
+class GenerateCloud(bpy.types.Operator):
+ bl_idname = "cloud.generate_cloud"
+ bl_label = "Generate Cloud"
+ bl_description = "Create a Cloud,Undo Cloud, or convert to Mesh Cloud depending on selection"
+ bl_register = True
+ bl_undo = True
+
+ @classmethod
+ def poll(cls, 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
+ blend_data = context.blend_data
+
+ # 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
+ maxPointDensityRadius = 1.5
+ scattering = 2.5
+ pointDensityRadiusFactor = 1.0
+ densityScale = 1.5
+
+ # What should we do?
+ WhatToDo = getActionToDo(active_object)
+
+ if WhatToDo == '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 blend_data 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.draw_type = 'SOLID'
+ eachMember.select = True
+ eachMember.hide_render = False
+
+ elif WhatToDo == 'CLOUD_CONVERT_TO_MESH':
+
+ cloudParticles = active_object.particle_systems.active
+
+ bounds = active_object.parent
+
+ ###############Create CloudPnts for putting points in#########
+ # Create a new object cloudPnts
+ cloudPnts = addNewObject(scene, "CloudPoints", bounds)
+ cloudPnts["CloudMember"] = "CreatedObj"
+ cloudPnts.draw_type = 'WIRE'
+ cloudPnts.hide_render = True
+
+ makeParent(bounds, cloudPnts, scene)
+
+ convertParticlesToMesh(scene, cloudParticles, cloudPnts, True)
+
+ removeParticleSystemFromObj(scene, active_object)
+
+ pDensity = getpdensitytexture(bounds)
+ pDensity.point_density.point_source = 'OBJECT'
+ pDensity.point_density.object = cloudPnts
+
+ #Let's resize the bound box to be more accurate.
+ how_much_bigger = pDensity.point_density.radius
+ makeObjectIntoBoundBox(scene, bounds, how_much_bigger, cloudPnts)
+
+ 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.draw_type = 'BOUNDS'
+ bounds.hide_render = False
+
+ # Just add a Definition Property designating this
+ # as the blend_data 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.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.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.draw_type = 'WIRE'
+ cloud.hide_render = True
+
+ makeParent(bounds, cloud, scene)
+
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.select_all(action='SELECT')
+
+ #Don't subdivide object or smooth if smoothing box not checked.
+ if scene.cloudsmoothing:
+ 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##################
+
+ # 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.particle_systems.active
+ cloudParticles.name = "CloudParticles"
+ cloudParticles.settings.frame_start = 0
+ cloudParticles.settings.frame_end = 0
+ cloudParticles.settings.emit_from = 'VOLUME'
+ cloudParticles.settings.lifetime = scene.frame_end
+ cloudParticles.settings.draw_method = 'DOT'
+ cloudParticles.settings.render_type = 'NONE'
+ cloudParticles.settings.distribution = 'RAND'
+ cloudParticles.settings.physics_type = 'NEWTON'
+ cloudParticles.settings.normal_factor = 0
+
+ #Gravity does not effect the particle system
+ eWeights = cloudParticles.settings.effector_weights
+ eWeights.gravity = 0
+
+ ####################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. Use itself as a reference.
+ makeObjectIntoBoundBox(scene, bounds, 1.0, bounds)
+
+ # 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 = blend_data.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.use_light_cache = True
+ mVolume.cache_resolution = 45
+
+ # Add a texture
+ vMaterialTextureSlots = cloudMaterial.texture_slots
+ cloudtex = blend_data.textures.new("CloudTex", type='CLOUDS')
+ cloudtex.noise_type = 'HARD_NOISE'
+ cloudtex.noise_scale = 2
+ mtex = cloudMaterial.texture_slots.add()
+ mtex.texture = cloudtex
+ mtex.texture_coords = 'ORCO'
+ mtex.use_map_color_diffuse = True
+
+ # Set time
+ scene.frame_current = 1
+
+ # Add a Point Density texture
+ pDensity = blend_data.textures.new("CloudPointDensity", 'POINT_DENSITY')
+
+ mtex = cloudMaterial.texture_slots.add()
+ mtex.texture = pDensity
+ mtex.texture_coords = 'GLOBAL'
+ mtex.use_map_density = True
+ mtex.use_rgb_to_intensity = True
+ mtex.texture_coords = 'GLOBAL'
+
+ pDensity.point_density.vertex_cache_space = 'WORLD_SPACE'
+ pDensity.point_density.use_turbulence = True
+ pDensity.point_density.noise_basis = 'VORONOI_F2'
+ pDensity.point_density.turbulence_depth = 3
+
+ pDensity.use_color_ramp = True
+ pRamp = pDensity.color_ramp
+ #pRamp.use_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.count = numParticles
+
+ pDensity.point_density.radius = (.00013764 * volumeBoundBox + .3989) * pointDensityRadiusFactor
+
+ if pDensity.point_density.radius > maxPointDensityRadius:
+ pDensity.point_density.radius = maxPointDensityRadius
+
+ # 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.draw_type = 'WIRE'
+ cloudPnts.hide_render = True
+
+ makeParent(bounds, cloudPnts, scene)
+
+ convertParticlesToMesh(scene, cloudParticles, cloudPnts, True)
+
+ # Add a modifier.
+ bpy.ops.object.modifier_add(type='DISPLACE')
+
+ cldPntsModifiers = cloudPnts.modifiers
+ cldPntsModifiers[0].name = "CloudPnts"
+ cldPntsModifiers[0].texture = cloudtex
+ cldPntsModifiers[0].texture_coords = '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.point_density.point_source = 'OBJECT'
+ pDensity.point_density.object = cloudPnts
+
+ removeParticleSystemFromObj(scene, cloud)
+
+ else:
+
+ pDensity.point_density.point_source = 'PARTICLE_SYSTEM'
+ pDensity.point_density.object = cloud
+ pDensity.point_density.particle_system = cloudParticles
+
+ if scene.cloud_type == '1': # Cumulous
+ print("Cumulous")
+ mVolume.density_scale = 2.22
+ pDensity.point_density.turbulence_depth = 10
+ pDensity.point_density.turbulence_strength = 6.3
+ pDensity.point_density.turbulence_scale = 2.9
+ pRampElements[1].position = .606
+ pDensity.point_density.radius = pDensity.point_density.radius + .1
+
+ elif scene.cloud_type == '2': # Cirrus
+ print("Cirrus")
+ pDensity.point_density.turbulence_strength = 22
+ mVolume.transmission_color = [3.5, 3.5, 3.5]
+ mVolume.scattering = .13
+
+ elif scene.cloud_type == '3': # Explosion
+ mVolume.emission = 1.42
+ mtex.use_rgb_to_intensity = False
+ pRampElements[0].position = .825
+ pRampElements[0].color = [.119,.119,.119,1]
+ pRampElements[1].position = .049
+ pRampElements[1].color = [1.0,1.0,1.0,0]
+ pDensity.point_density.turbulence_strength = 1.5
+ pRampElement1 = pRampElements.new(.452)
+ pRampElement1.color = [.814,.112,0,1]
+ pRampElement2 = pRampElements.new(.234)
+ pRampElement2.color = [.814,.310,.002,1]
+ pRampElement3 = pRampElements.new(.669)
+ pRampElement3.color = [0,.0,.040,1]
+
+ # Select the object.
+ bounds.select = True
+ scene.objects.active = bounds
+
+ #Let's resize the bound box to be more accurate.
+ how_much_bigger = pDensity.point_density.radius + .1
+
+ #If it's a particle cloud use cloud mesh if otherwise use point mesh
+ if not scene.cloudparticles:
+ makeObjectIntoBoundBox(scene, bounds, how_much_bigger, cloudPnts)
+ else:
+ makeObjectIntoBoundBox(scene, bounds, how_much_bigger, cloud)
+
+ return {'FINISHED'}
+
+
+def register():
+ bpy.types.Scene.cloudparticles = BoolProperty(
+ name="Particles",
+ description="Generate Cloud as Particle System",
+ default=False)
+
+ bpy.types.Scene.cloudsmoothing = BoolProperty(
+ name="Smoothing",
+ description="Smooth Resultant Geometry From Gen Cloud Operation",
+ default=True)
+
+ bpy.types.Scene.cloud_type = EnumProperty(
+ name="Type",
+ description="Select the type of cloud to create with material settings",
+ items=[("0","Stratus","Generate Stratus_foggy Cloud"),
+ ("1","Cumulous","Generate Cumulous_puffy Cloud"),
+ ("2","Cirrus","Generate Cirrus_wispy Cloud"),
+ ("3","Explosion","Generate Explosion"),
+ ],
+ default='0')
+
+
+def unregister():
+ del bpy.types.Scene.cloudparticles
+ del bpy.types.Scene.cloud_type
+
+
+if __name__ == "__main__":
+ register()
diff --git a/object_fracture/__init__.py b/object_fracture/__init__.py
new file mode 100644
index 00000000..77269c7f
--- /dev/null
+++ b/object_fracture/__init__.py
@@ -0,0 +1,78 @@
+# ##### 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": "Fracture Tools",
+ "author": "pildanovak",
+ "version": (2,0),
+ "blender": (2, 5, 3),
+ "api": 31965,
+ "location": "Fracture tools (Search > Fracture Object & Add -> Fracture Helper Objects",
+ "description": "Fractured Object, Bomb, Projectile, Recorder",
+ "warning": "",
+ "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"}
+
+
+if "bpy" in locals():
+ import imp
+ imp.reload(fracture_ops)
+ imp.reload(fracture_setup)
+else:
+ from . import fracture_ops
+ from . import fracture_setup
+
+import bpy
+
+
+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
+
+
+def menu_func(self, context):
+ self.layout.menu("INFO_MT_add_fracture_objects", icon="PLUGIN")
+
+
+def register():
+ # Add the "add fracture objects" menu to the "Add" menu
+ space_info.INFO_MT_add.append(menu_func)
+
+
+def unregister():
+ # 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..820b7d65
--- /dev/null
+++ b/object_fracture/fracture_ops.py
@@ -0,0 +1,490 @@
+# ##### 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),
+ layers=(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.vertices:
+ 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),
+ layers=(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.vertices:
+ 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.vertices:
+ 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') is 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.vertices:
+ 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.vertices:
+ for v in f.vertices:
+ if v == i:
+ for v1 in f.vertices:
+ 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.vertices) - 1, -1, -1):
+ if vgi[x] != gi:
+ #print('getIslands: selecting')
+ #print('getIslands: ' + str(x))
+ a.data.vertices[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.vertices) > 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)
+ if self.exe:
+ fracture_basic(context,
+ self.nshards,
+ self.crack_type,
+ self.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.exe:
+ fracture_group(context, self.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..3ab93821
--- /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_settings.all_frames
+
+ tobeprocessed = []
+ for ob in sce.objects:
+ if ob.select:
+ tobeprocessed.append(ob)
+
+ for ob in tobeprocessed:
+ g = ob.game
+
+ g.physics_type = 'RIGID_BODY'
+ g.use_collision_bounds = 1
+ g.collision_bounds_type = '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_povray/__init__.py b/render_povray/__init__.py
new file mode 100644
index 00000000..9d1e73b8
--- /dev/null
+++ b/render_povray/__init__.py
@@ -0,0 +1,226 @@
+# ##### 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": "PovRay 3.7",
+ "author": "Campbell Barton, Silvio Falcinelli, Maurice Raybaud",
+ "version": (0, 0, 3),
+ "blender": (2, 5, 4),
+ "api": 31667,
+ "location": "Info Header (engine dropdown)",
+ "description": "Basic povray 3.7 integration for blender",
+ "warning": "both povray 3.7 and this script are beta",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Render/PovRay",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&atid=468&aid=22717&group_id=153",
+ "category": "Render"}
+
+
+if "bpy" in locals():
+ import imp
+ imp.reload(ui)
+ imp.reload(render)
+
+else:
+ import bpy
+ from bpy.props import *
+ from render_povray import ui
+ from render_povray import render
+
+def register():
+ Scene = bpy.types.Scene
+
+ # Not a real pov option, just to know if we should write
+ Scene.pov_radio_enable = BoolProperty(
+ name="Enable Radiosity",
+ description="Enable povrays radiosity calculation",
+ default=False)
+ Scene.pov_radio_display_advanced = BoolProperty(
+ name="Advanced Options",
+ description="Show advanced options",
+ default=False)
+ Scene.pov_baking_enable = BoolProperty(
+ name="Enable Baking",
+ description="Enable povrays texture baking",
+ default=False)
+
+ # Real pov options
+ Scene.pov_radio_adc_bailout = FloatProperty(
+ name="ADC Bailout", description="The adc_bailout for radiosity rays. Use adc_bailout = 0.01 / brightest_ambient_object for good results",
+ min=0.0, max=1000.0, soft_min=0.0, soft_max=1.0, default=0.01)
+
+ Scene.pov_radio_always_sample = BoolProperty(
+ name="Always Sample", description="Only use the data from the pretrace step and not gather any new samples during the final radiosity pass",
+ default=True)
+
+ Scene.pov_radio_brightness = FloatProperty(
+ name="Brightness", description="Amount objects are brightened before being returned upwards to the rest of the system",
+ min=0.0, max=1000.0, soft_min=0.0, soft_max=10.0, default=1.0)
+
+ Scene.pov_radio_count = IntProperty(
+ name="Ray Count", description="Number of rays that are sent out whenever a new radiosity value has to be calculated",
+ min=1, max=1600, default=35)
+
+ Scene.pov_radio_error_bound = FloatProperty(
+ name="Error Bound", description="One of the two main speed/quality tuning values, lower values are more accurate",
+ min=0.0, max=1000.0, soft_min=0.1, soft_max=10.0, default=1.8)
+
+ Scene.pov_radio_gray_threshold = FloatProperty(
+ name="Gray Threshold", description="One of the two main speed/quality tuning values, lower values are more accurate",
+ min=0.0, max=1.0, soft_min=0, soft_max=1, default=0.0)
+
+ Scene.pov_radio_low_error_factor = FloatProperty(
+ name="Low Error Factor", description="If you calculate just enough samples, but no more, you will get an image which has slightly blotchy lighting",
+ min=0.0, max=1.0, soft_min=0.0, soft_max=1.0, default=0.5)
+
+ # max_sample - not available yet
+ Scene.pov_radio_media = BoolProperty(
+ name="Media", description="Radiosity estimation can be affected by media",
+ default=False)
+
+ Scene.pov_radio_minimum_reuse = FloatProperty(
+ name="Minimum Reuse", description="Fraction of the screen width which sets the minimum radius of reuse for each sample point (At values higher than 2% expect errors)",
+ min=0.0, max=1.0, soft_min=0.1, soft_max=0.1, default=0.015)
+
+ Scene.pov_radio_nearest_count = IntProperty(
+ name="Nearest Count", description="Number of old ambient values blended together to create a new interpolated value",
+ min=1, max=20, default=5)
+
+ Scene.pov_radio_normal = BoolProperty(
+ name="Normals", description="Radiosity estimation can be affected by normals",
+ default=False)
+
+ Scene.pov_radio_recursion_limit = IntProperty(
+ name="Recursion Limit", description="how many recursion levels are used to calculate the diffuse inter-reflection",
+ min=1, max=20, default=3)
+
+ ########################################MR######################################
+ Mat = bpy.types.Material
+
+ Mat.pov_irid_enable = BoolProperty(
+ name="Enable Iridescence",
+ description="Newton's thin film interference (like an oil slick on a puddle of water or the rainbow hues of a soap bubble.)",
+ default=False)
+
+ Mat.pov_mirror_use_IOR = BoolProperty(
+ name="Correct Reflection",
+ description="Use same IOR as raytrace transparency to calculate mirror reflections. More physically correct",
+ default=False)
+
+ Mat.pov_mirror_metallic = BoolProperty(
+ name="Metallic Reflection",
+ description="mirror reflections get colored as diffuse (for metallic materials)",
+ default=False)
+
+ Mat.pov_conserve_energy = BoolProperty(
+ name="Conserve Energy",
+ description="Light transmitted is more correctly reduced by mirror reflections, also the sum of diffuse and translucency gets reduced below one ",
+ default=True)
+
+ Mat.pov_irid_amount = FloatProperty(
+ name="amount",
+ description="Contribution of the iridescence effect to the overall surface color. As a rule of thumb keep to around 0.25 (25% contribution) or less, but experiment. If the surface is coming out too white, try lowering the diffuse and possibly the ambient values of the surface.",
+ min=0.0, max=1.0, soft_min=0.01, soft_max=1.0, default=0.25)
+
+ Mat.pov_irid_thickness = FloatProperty(
+ name="thickness",
+ description="A very thin film will have a high frequency of color changes while a thick film will have large areas of color.",
+ min=0.0, max=1000.0, soft_min=0.1, soft_max=10.0, default=1)
+
+ Mat.pov_irid_turbulence = FloatProperty(
+ name="turbulence",
+ description="This parameter varies the thickness.",
+ min=0.0, max=10.0, soft_min=0.000, soft_max=1.0, default=0)
+
+ Mat.pov_caustics_enable = BoolProperty(
+ name="Caustics",
+ description="use only fake refractive caustics (default) or photon based reflective/refractive caustics",
+ default=True)
+
+ Mat.pov_fake_caustics = BoolProperty(
+ name="Fake Caustics",
+ description="use only (Fast) fake refractive caustics",
+ default=True)
+
+ Mat.pov_fake_caustics_power = FloatProperty(
+ name="Fake caustics power",
+ description="Values typically range from 0.0 to 1.0 or higher. Zero is no caustics. Low, non-zero values give broad hot-spots while higher values give tighter, smaller simulated focal points",
+ min=0.00, max=10.0, soft_min=0.00, soft_max=1.10, default=0.1)
+
+ Mat.pov_photons_refraction = BoolProperty(
+ name="Refractive Photon Caustics",
+ description="more physically correct",
+ default=False)
+
+ Mat.pov_photons_dispersion = FloatProperty(
+ name="chromatic dispersion",
+ description="Light passing through will be separated according to wavelength. This ratio of refractive indices for violet to red controls how much the colors are spread out 1 = no dispersion, good values are 1.01 to 1.1",
+ min=1.00, max=10.0, soft_min=1.00, soft_max=1.10, default=1.00)
+
+ Mat.pov_photons_reflection = BoolProperty(
+ name="Reflective Photon Caustics",
+ description="Use this to make your Sauron's ring ;-P",
+ default=False)
+
+ Mat.pov_refraction_type = EnumProperty(
+ items=[("0","None","use only reflective caustics"),
+ ("1","Fake Caustics","use fake caustics"),
+ ("2","Photons Caustics","use photons for refractive caustics"),
+ ],
+ name="Refractive",
+ description="use fake caustics (fast) or true photons for refractive Caustics",
+ default="1")#ui.py has to be loaded before render.py with this.
+ ######################################EndMR#####################################
+
+def unregister():
+ import bpy
+ Scene = bpy.types.Scene
+ Mat = bpy.types.Material # MR
+ del Scene.pov_radio_enable
+ del Scene.pov_radio_display_advanced
+ del Scene.pov_radio_adc_bailout
+ del Scene.pov_radio_always_sample
+ del Scene.pov_radio_brightness
+ del Scene.pov_radio_count
+ del Scene.pov_radio_error_bound
+ del Scene.pov_radio_gray_threshold
+ del Scene.pov_radio_low_error_factor
+ del Scene.pov_radio_media
+ del Scene.pov_radio_minimum_reuse
+ del Scene.pov_radio_nearest_count
+ del Scene.pov_radio_normal
+ del Scene.pov_radio_recursion_limit
+ del Scene.pov_baking_enable # MR
+ del Mat.pov_irid_enable # MR
+ del Mat.pov_mirror_use_IOR # MR
+ del Mat.pov_mirror_metallic # MR
+ del Mat.pov_conserve_energy # MR
+ del Mat.pov_irid_amount # MR
+ del Mat.pov_irid_thickness # MR
+ del Mat.pov_irid_turbulence # MR
+ del Mat.pov_caustics_enable # MR
+ del Mat.pov_fake_caustics # MR
+ del Mat.pov_fake_caustics_power # MR
+ del Mat.pov_photons_refraction # MR
+ del Mat.pov_photons_dispersion # MR
+ del Mat.pov_photons_reflection # MR
+ del Mat.pov_refraction_type # MR
+
+if __name__ == "__main__":
+ register()
diff --git a/render_povray/render.py b/render_povray/render.py
new file mode 100644
index 00000000..ed567784
--- /dev/null
+++ b/render_povray/render.py
@@ -0,0 +1,1447 @@
+# ##### 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
+import subprocess
+import os
+import sys
+import time
+import math
+from math import atan, pi, degrees, sqrt
+
+import platform as pltfrm
+if pltfrm.architecture()[0] == '64bit':
+ bitness = 64
+else:
+ bitness = 32
+
+##############################SF###########################
+##############find image texture
+def splitExt(path):
+ dotidx = path.rfind('.')
+ if dotidx == -1:
+ return path, ''
+ else:
+ return (path[dotidx:]).upper().replace('.','')
+
+
+def imageFormat(imgF):
+ ext = ""
+ ext_orig = splitExt(imgF)
+ if ext_orig == 'JPG' or ext_orig == 'JPEG': ext='jpeg'
+ if ext_orig == 'GIF': ext = 'gif'
+ if ext_orig == 'TGA': ext = 'tga'
+ if ext_orig == 'IFF': ext = 'iff'
+ if ext_orig == 'PPM': ext = 'ppm'
+ if ext_orig == 'PNG': ext = 'png'
+ if ext_orig == 'SYS': ext = 'sys'
+ if ext_orig in ('TIFF', 'TIF'): ext = 'tiff'
+ if ext_orig == 'EXR': ext = 'exr'#POV3.7 Only!
+ if ext_orig == 'HDR': ext = 'hdr'#POV3.7 Only! --MR
+ print(imgF)
+ if not ext: print(' WARNING: texture image format not supported ') # % (imgF , '')) #(ext_orig)))
+ return ext
+
+def imgMap(ts):
+ image_map=''
+ if ts.mapping=='FLAT':image_map= ' map_type 0 '
+ if ts.mapping=='SPHERE':image_map= ' map_type 1 '# map_type 7 in megapov
+ if ts.mapping=='TUBE':image_map= ' map_type 2 '
+ #if ts.mapping=='?':image_map= ' map_type 3 '# map_type 3 and 4 in development (?) for Povray, currently they just seem to default back to Flat (type 0)
+ #if ts.mapping=='?':image_map= ' map_type 4 '# map_type 3 and 4 in development (?) for Povray, currently they just seem to default back to Flat (type 0)
+ if ts.texture.use_interpolation: image_map+= " interpolate 2 "
+ if ts.texture.extension == 'CLIP': image_map+=' once '
+ #image_map+='}'
+ #if ts.mapping=='CUBE':image_map+= 'warp { cubic } rotate <-90,0,180>' #no direct cube type mapping. Though this should work in POV 3.7 it doesn't give that good results(best suited to environment maps?)
+ #if image_map=='': print(' No texture image found ')
+ return image_map
+
+def imgMapBG(wts):
+ image_mapBG=''
+ if wts.texture_coords== 'VIEW':image_mapBG= ' map_type 0 ' #texture_coords refers to the mapping of world textures
+ if wts.texture_coords=='ANGMAP':image_mapBG= ' map_type 1 '
+ if wts.texture_coords=='TUBE':image_mapBG= ' map_type 2 '
+ if wts.texture.use_interpolation: image_mapBG+= " interpolate 2 "
+ if wts.texture.extension == 'CLIP': image_mapBG+=' once '
+ #image_mapBG+='}'
+ #if wts.mapping=='CUBE':image_mapBG+= 'warp { cubic } rotate <-90,0,180>' #no direct cube type mapping. Though this should work in POV 3.7 it doesn't give that good results(best suited to environment maps?)
+ #if image_mapBG=='': print(' No background texture image found ')
+ return image_mapBG
+
+def splitFile(path):
+ idx = path.rfind('/')
+ if idx == -1:
+ idx = path.rfind('\\')
+ return path[idx:].replace("/", "").replace("\\", "")
+
+def splitPath(path):
+ idx = path.rfind('/')
+ if idx == -1:
+ return path, ''
+ else:
+ return path[:idx]
+
+def findInSubDir(filename, subdirectory=''):
+ pahFile=''
+ if subdirectory:
+ path = subdirectory
+ else:
+ path = os.getcwd()
+ try:
+ for root, dirs, names in os.walk(path):
+ if filename in names:
+ pahFile = os.path.join(root, filename)
+ return pahFile
+ except OSError:
+ return ''
+
+def path_image(image):
+ import os
+ fn = bpy.path.abspath(image)
+ fn_strip = os.path.basename(fn)
+ if not os.path.isfile(fn):
+ fn=(findInSubDir(splitFile(fn),splitPath(bpy.data.filepath)))
+ ()
+ return fn
+
+##############end find image texture
+
+def splitHyphen(name):
+ hyphidx = name.find('-')
+ if hyphidx == -1:
+ return name
+ else:
+ return (name[hyphidx:]).replace('-','')
+
+##############safety string name material
+def safety(name, Level):
+ # Level=1 is for texture with No specular nor Mirror reflection
+ # Level=2 is for texture with translation of spec and mir levels for when no map influences them
+ # Level=3 is for texture with Maximum Spec and Mirror
+
+ try:
+ if int(name) > 0:
+ prefix='shader'
+ except:
+ prefix = ''
+ prefix='shader_'
+ name = splitHyphen(name)
+ if Level == 2:
+ return prefix+name
+ elif Level == 1:
+ return prefix+name+'0'#used for 0 of specular map
+ elif Level == 3:
+ return prefix+name+'1'#used for 1 of specular map
+
+
+##############end safety string name material
+##############################EndSF###########################
+
+def write_pov(filename, scene=None, info_callback=None):
+ file = open(filename, 'w')
+
+ # Only for testing
+ if not scene:
+ scene = bpy.data.scenes[0]
+
+ render = scene.render
+ world = scene.world
+
+ def uniqueName(name, nameSeq):
+
+ if name not in nameSeq:
+ return name
+
+ name_orig = name
+ i = 1
+ while name in nameSeq:
+ name = '%s_%.3d' % (name_orig, i)
+ i += 1
+ name = splitHyphen(name)
+ return name
+
+ def writeMatrix(matrix):
+ file.write('\tmatrix <%.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f, %.6f>\n' %\
+ (matrix[0][0], matrix[0][1], matrix[0][2], matrix[1][0], matrix[1][1], matrix[1][2], matrix[2][0], matrix[2][1], matrix[2][2], matrix[3][0], matrix[3][1], matrix[3][2]))
+
+ def writeObjectMaterial(material):
+
+ # DH - modified some variables to be function local, avoiding RNA write
+ # this should be checked to see if it is functionally correct
+
+ if material: #and material.transparency_method == 'RAYTRACE':#Commented out: always write IOR to be able to use it for SSS, Fresnel reflections...
+ #But there can be only one!
+ if material.subsurface_scattering.use:#SSS IOR get highest priority
+ file.write('\tinterior { ior %.6f\n' % material.subsurface_scattering.ior)
+ elif material.pov_mirror_use_IOR:#Then the raytrace IOR taken from raytrace transparency properties and used for reflections if IOR Mirror option is checked
+ file.write('\tinterior { ior %.6f\n' % material.raytrace_transparency.ior)
+ else:
+ file.write('\tinterior { ior %.6f\n' % material.raytrace_transparency.ior)
+
+ pov_fake_caustics = False
+ pov_photons_refraction = False
+ pov_photons_reflection = False
+
+ if material.pov_refraction_type=="0":
+ pov_fake_caustics = False
+ pov_photons_refraction = False
+ pov_photons_reflection = True #should respond only to proper checkerbox
+ elif material.pov_refraction_type=="1":
+ pov_fake_caustics = True
+ pov_photons_refraction = False
+ elif material.pov_refraction_type=="2":
+ pov_fake_caustics = False
+ pov_photons_refraction = True
+
+ #If only Raytrace transparency is set, its IOR will be used for refraction, but user can set up "un-physical" fresnel reflections in raytrace mirror parameters.
+ #Last, if none of the above is specified, user can set up "un-physical" fresnel reflections in raytrace mirror parameters. And pov IOR defaults to 1.
+ if material.pov_caustics_enable:
+ if pov_fake_caustics:
+ file.write('\tcaustics %.3g\n' % material.pov_fake_caustics_power)
+ if pov_photons_refraction:
+ file.write('\tdispersion %.3g\n' % material.pov_photons_dispersion) #Default of 1 means no dispersion
+ #TODO
+ # Other interior args
+ # if material.use_transparency and material.transparency_method == 'RAYTRACE':
+ # fade_distance 2
+ # fade_power [Value]
+ # fade_color
+
+ # (variable) dispersion_samples (constant count for now)
+ file.write('\t}\n')
+ if pov_photons_refraction or pov_photons_reflection:
+ file.write('\tphotons{\n')
+ file.write('\t\ttarget\n')
+ if pov_photons_refraction:
+ file.write('\t\trefraction on\n')
+ if pov_photons_reflection:
+ file.write('\t\treflection on\n')
+ file.write('\t}\n')
+
+ materialNames = {}
+ DEF_MAT_NAME = 'Default'
+
+ def writeMaterial(material):
+ # Assumes only called once on each material
+
+ if material:
+ name_orig = material.name
+ else:
+ name_orig = DEF_MAT_NAME
+
+ name = materialNames[name_orig] = uniqueName(bpy.path.clean_name(name_orig), materialNames)
+
+
+ ##################Several versions of the finish: Level conditions are variations for specular/Mirror texture channel map with alternative finish of 0 specular and no mirror reflection
+ # Level=1 Means No specular nor Mirror reflection
+ # Level=2 Means translation of spec and mir levels for when no map influences them
+ # Level=3 Means Maximum Spec and Mirror
+ def povHasnoSpecularMaps(Level):
+ if Level == 2:
+ file.write('#declare %s = finish {\n' % safety(name, Level = 2))
+ elif Level == 1:
+ file.write('#declare %s = finish {\n' % safety(name, Level = 1))
+ elif Level == 3:
+ file.write('#declare %s = finish {\n' % safety(name, Level = 3))
+
+
+ if material:
+ #Povray 3.7 now uses two diffuse values respectively for front and back shading (the back diffuse is like blender translucency)
+ frontDiffuse=material.diffuse_intensity
+ backDiffuse=material.translucency
+
+
+ if material.pov_conserve_energy:
+
+ #Total should not go above one
+ if (frontDiffuse + backDiffuse) <= 1.0:
+ pass
+ elif frontDiffuse==backDiffuse:
+ frontDiffuse = backDiffuse = 0.5 # Try to respect the user's "intention" by comparing the two values but bringing the total back to one
+ elif frontDiffuse>backDiffuse: # Let the highest value stay the highest value
+ backDiffuse = 1-(1-frontDiffuse)
+ else:
+ frontDiffuse = 1-(1-backDiffuse)
+
+
+ # map hardness between 0.0 and 1.0
+ roughness = ((1.0 - ((material.specular_hardness - 1.0) / 510.0)))
+ ## scale from 0.0 to 0.1
+ roughness *= 0.1
+ # add a small value because 0.0 is invalid
+ roughness += (1 / 511.0)
+
+ #####################################Diffuse Shader######################################
+ # Not used for Full spec (Level=3) of the shader
+ if material.diffuse_shader == 'OREN_NAYAR' and Level != 3:
+ file.write('\tbrilliance %.3g\n' % (0.9+material.roughness))#blender roughness is what is generally called oren nayar Sigma, and brilliance in povray
+
+ if material.diffuse_shader == 'TOON' and Level != 3:
+ file.write('\tbrilliance %.3g\n' % (0.01+material.diffuse_toon_smooth*0.25))
+ frontDiffuse*=0.5 #Lower diffuse and increase specular for toon effect seems to look better in povray
+
+ if material.diffuse_shader == 'MINNAERT' and Level != 3:
+ #file.write('\taoi %.3g\n' % material.darkness)
+ pass #let's keep things simple for now
+ if material.diffuse_shader == 'FRESNEL' and Level != 3:
+ #file.write('\taoi %.3g\n' % material.diffuse_fresnel_factor)
+ pass #let's keep things simple for now
+ if material.diffuse_shader == 'LAMBERT' and Level != 3:
+ file.write('\tbrilliance 1.8\n') #trying to best match lambert attenuation by that constant brilliance value
+
+ if Level == 2:
+ ####################################Specular Shader######################################
+ if material.specular_shader == 'COOKTORR' or material.specular_shader == 'PHONG':#No difference between phong and cook torrence in blender HaHa!
+ file.write('\tphong %.3g\n' % (material.specular_intensity))
+ file.write('\tphong_size %.3g\n'% (material.specular_hardness / 2 + 0.25))
+
+ if material.specular_shader == 'BLINN':#Povray "specular" keyword corresponds to a Blinn model, without the ior.
+ file.write('\tspecular %.3g\n' % (material.specular_intensity * (material.specular_ior/4))) #Use blender Blinn's IOR just as some factor for spec intensity
+ file.write('\troughness %.3g\n' % roughness)
+ #Could use brilliance 2(or varying around 2 depending on ior or factor) too.
+
+
+ if material.specular_shader == 'TOON':
+ file.write('\tphong %.3g\n' % (material.specular_intensity * 2))
+ file.write('\tphong_size %.3g\n' % (0.1+material.specular_toon_smooth / 2)) #use extreme phong_size
+
+
+ if material.specular_shader == 'WARDISO':
+ file.write('\tspecular %.3g\n' % (material.specular_intensity / (material.specular_slope+0.0005))) #find best suited default constant for brilliance Use both phong and specular for some values.
+ file.write('\troughness %.4g\n' % (0.0005+material.specular_slope/10)) #find best suited default constant for brilliance Use both phong and specular for some values.
+ file.write('\tbrilliance %.4g\n' % (1.8-material.specular_slope*1.8)) #find best suited default constant for brilliance Use both phong and specular for some values.
+
+
+
+ #########################################################################################
+ elif Level == 1:
+ file.write('\tspecular 0\n')
+ elif Level == 3:
+ file.write('\tspecular 1\n')
+ file.write('\tdiffuse %.3g %.3g\n' % (frontDiffuse, backDiffuse))
+
+
+ file.write('\tambient %.3g\n' % material.ambient)
+ #file.write('\tambient rgb <%.3g, %.3g, %.3g>\n' % tuple([c*material.ambient for c in world.ambient_color])) # povray blends the global value
+ file.write('\temission %.3g\n' % material.emit) #New in povray 3.7
+
+ #file.write('\troughness %.3g\n' % roughness) #povray just ignores roughness if there's no specular keyword
+
+ if material.pov_conserve_energy:
+ file.write('\tconserve_energy\n')#added for more realistic shading. Needs some checking to see if it really works. --Maurice.
+
+ # 'phong 70.0 '
+ if Level != 1:
+ if material.raytrace_mirror.use:
+ raytrace_mirror = material.raytrace_mirror
+ if raytrace_mirror.reflect_factor:
+ file.write('\treflection {\n')
+ file.write('\t\trgb <%.3g, %.3g, %.3g>' % tuple(material.mirror_color))
+ if material.pov_mirror_metallic:
+ file.write('\t\tmetallic %.3g' % (raytrace_mirror.reflect_factor))
+ if material.pov_mirror_use_IOR: #WORKING ?
+ file.write('\t\tfresnel 1 ')#Removed from the line below: gives a more physically correct material but needs proper IOR. --Maurice
+ file.write('\t\tfalloff %.3g exponent %.3g} ' % (raytrace_mirror.fresnel, raytrace_mirror.fresnel_factor))
+
+ if material.subsurface_scattering.use:
+ subsurface_scattering = material.subsurface_scattering
+ file.write('\tsubsurface { <%.3g, %.3g, %.3g>, <%.3g, %.3g, %.3g> }\n' % (sqrt(subsurface_scattering.radius[0])*1.5, sqrt(subsurface_scattering.radius[1])*1.5, sqrt(subsurface_scattering.radius[2])*1.5, 1-subsurface_scattering.color[0], 1-subsurface_scattering.color[1], 1-subsurface_scattering.color[2]))
+
+ if material.pov_irid_enable:
+ file.write('\tirid { %.4g thickness %.4g turbulence %.4g }' % (material.pov_irid_amount, material.pov_irid_thickness, material.pov_irid_turbulence))
+
+ else:
+ file.write('\tdiffuse 0.8\n')
+ file.write('\tphong 70.0\n')
+
+ #file.write('\tspecular 0.2\n')
+
+
+ # This is written into the object
+ '''
+ if material and material.transparency_method=='RAYTRACE':
+ 'interior { ior %.3g} ' % material.raytrace_transparency.ior
+ '''
+
+ #file.write('\t\t\tcrand 1.0\n') # Sand granyness
+ #file.write('\t\t\tmetallic %.6f\n' % material.spec)
+ #file.write('\t\t\tphong %.6f\n' % material.spec)
+ #file.write('\t\t\tphong_size %.6f\n' % material.spec)
+ #file.write('\t\t\tbrilliance %.6f ' % (material.specular_hardness/256.0) # Like hardness
+
+ file.write('}\n')
+
+ # Level=1 Means No specular nor Mirror reflection
+ povHasnoSpecularMaps(Level=1)
+
+ # Level=2 Means translation of spec and mir levels for when no map influences them
+ povHasnoSpecularMaps(Level=2)
+
+ # Level=3 Means Maximum Spec and Mirror
+ povHasnoSpecularMaps(Level=3)
+
+ def exportCamera():
+ camera = scene.camera
+
+ # DH disabled for now, this isn't the correct context
+ active_object = None #bpy.context.active_object # does not always work MR
+ matrix = camera.matrix_world
+ focal_point = camera.data.dof_distance
+
+ # compute resolution
+ Qsize = float(render.resolution_x) / float(render.resolution_y)
+ file.write('#declare camLocation = <%.6f, %.6f, %.6f>;\n' % (matrix[3][0], matrix[3][1], matrix[3][2]))
+ file.write('#declare camLookAt = <%.6f, %.6f, %.6f>;\n' % tuple([degrees(e) for e in matrix.rotation_part().to_euler()]))
+
+ file.write('camera {\n')
+ if scene.pov_baking_enable and active_object and active_object.type=='MESH':
+ file.write('\tmesh_camera{ 1 3\n') # distribution 3 is what we want here
+ file.write('\t\tmesh{%s}\n' % active_object.name)
+ file.write('\t}\n')
+ file.write('location <0,0,.01>')
+ file.write('direction <0,0,-1>')
+ # Using standard camera otherwise
+ else:
+ file.write('\tlocation <0, 0, 0>\n')
+ file.write('\tlook_at <0, 0, -1>\n')
+ file.write('\tright <%s, 0, 0>\n' % - Qsize)
+ file.write('\tup <0, 1, 0>\n')
+ file.write('\tangle %f \n' % (360.0 * atan(16.0 / camera.data.lens) / pi))
+
+ file.write('\trotate <%.6f, %.6f, %.6f>\n' % tuple([degrees(e) for e in matrix.rotation_part().to_euler()]))
+ file.write('\ttranslate <%.6f, %.6f, %.6f>\n' % (matrix[3][0], matrix[3][1], matrix[3][2]))
+ if focal_point != 0:
+ file.write('\taperture 0.25\n') # fixed blur amount for now to do, add slider a button?
+ file.write('\tblur_samples 96 128\n')
+ file.write('\tvariance 1/10000\n')
+ file.write('\tfocal_point <0, 0, %f>\n' % focal_point)
+ file.write('}\n')
+
+ def exportLamps(lamps):
+ # Get all lamps
+ for ob in lamps:
+ lamp = ob.data
+
+ matrix = ob.matrix_world
+
+ color = tuple([c * lamp.energy *2 for c in lamp.color]) # Colour is modified by energy #muiltiplie by 2 for a better match --Maurice
+
+ file.write('light_source {\n')
+ file.write('\t< 0,0,0 >\n')
+ file.write('\tcolor rgb<%.3g, %.3g, %.3g>\n' % color)
+
+ if lamp.type == 'POINT': # Point Lamp
+ pass
+ elif lamp.type == 'SPOT': # Spot
+ file.write('\tspotlight\n')
+
+ # Falloff is the main radius from the centre line
+ file.write('\tfalloff %.2f\n' % (degrees(lamp.spot_size) / 2.0)) # 1 TO 179 FOR BOTH
+ file.write('\tradius %.6f\n' % ((degrees(lamp.spot_size) / 2.0) * (1.0 - lamp.spot_blend)))
+
+ # Blender does not have a tightness equivilent, 0 is most like blender default.
+ file.write('\ttightness 0\n') # 0:10f
+
+ file.write('\tpoint_at <0, 0, -1>\n')
+ elif lamp.type == 'SUN':
+ file.write('\tparallel\n')
+ file.write('\tpoint_at <0, 0, -1>\n') # *must* be after 'parallel'
+
+ elif lamp.type == 'AREA':
+ file.write('\tfade_distance %.6f\n' % (lamp.distance / 5) )
+ file.write('\tfade_power %d\n' % 2) # Area lights have no falloff type, so always use blenders lamp quad equivalent for those?
+ size_x = lamp.size
+ samples_x = lamp.shadow_ray_samples_x
+ if lamp.shape == 'SQUARE':
+ size_y = size_x
+ samples_y = samples_x
+ else:
+ size_y = lamp.size_y
+ samples_y = lamp.shadow_ray_samples_y
+
+ file.write('\tarea_light <%d,0,0>,<0,0,%d> %d, %d\n' % (size_x, size_y, samples_x, samples_y))
+ if lamp.shadow_ray_sample_method == 'CONSTANT_JITTERED':
+ if lamp.jitter:
+ file.write('\tjitter\n')
+ else:
+ file.write('\tadaptive 1\n')
+ file.write('\tjitter\n')
+
+ if lamp.type == 'HEMI':#HEMI never has any shadow attribute
+ file.write('\tshadowless\n')
+ elif lamp.shadow_method == 'NOSHADOW':
+ file.write('\tshadowless\n')
+
+ if lamp.type != 'SUN' and lamp.type!='AREA' and lamp.type!='HEMI':#Sun shouldn't be attenuated. Hemi and area lights have no falloff attribute so they are put to type 2 attenuation a little higher above.
+ file.write('\tfade_distance %.6f\n' % (lamp.distance / 5) )
+ if lamp.falloff_type == 'INVERSE_SQUARE':
+ file.write('\tfade_power %d\n' % 2) # Use blenders lamp quad equivalent
+ elif lamp.falloff_type == 'INVERSE_LINEAR':
+ file.write('\tfade_power %d\n' % 1) # Use blenders lamp linear
+ elif lamp.falloff_type == 'CONSTANT': #Supposing using no fade power keyword would default to constant, no attenuation.
+ pass
+ elif lamp.falloff_type == 'CUSTOM_CURVE': #Using Custom curve for fade power 3 for now.
+ file.write('\tfade_power %d\n' % 4)
+
+ writeMatrix(matrix)
+
+ file.write('}\n')
+##################################################################################################################################
+#Wip to be Used for fresnel, but not tested yet.
+##################################################################################################################################
+## lampLocation=[0,0,0]
+## lampRotation=[0,0,0]
+## lampDistance=0.00
+## averageLampLocation=[0,0,0]
+## averageLampRotation=[0,0,0]
+## averageLampDistance=0.00
+## lamps=[]
+## for l in scene.objects:
+## if l.type == 'LAMP':#get all lamps
+## lamps += [l]
+## for ob in lamps:
+## lamp = ob.data
+## lampLocation[0]+=ob.location[0]
+## lampLocation[1]+=ob.location[1]
+## lampLocation[2]+=ob.location[2]
+## lampRotation[0]+=ob.rotation_euler[0]
+## lampRotation[1]+=ob.rotation_euler[1]
+## lampRotation[2]+=ob.rotation_euler[2]
+## lampDistance+=ob.data.distance
+## averageLampRotation[0]=lampRotation[0] / len(lamps)#create an average direction for all lamps.
+## averageLampRotation[1]=lampRotation[1] / len(lamps)#create an average direction for all lamps.
+## averageLampRotation[2]=lampRotation[2] / len(lamps)#create an average direction for all lamps.
+##
+## averageLampLocation[0]=lampLocation[0] / len(lamps)#create an average position for all lamps.
+## averageLampLocation[1]=lampLocation[1] / len(lamps)#create an average position for all lamps.
+## averageLampLocation[2]=lampLocation[2] / len(lamps)#create an average position for all lamps.
+##
+## averageLampDistance=lampDistance / len(lamps)#create an average distance for all lamps.
+## file.write('\n#declare lampTarget= vrotate(<%.4g,%.4g,%.4g>,<%.4g,%.4g,%.4g>);' % (-(averageLampLocation[0]-averageLampDistance), -(averageLampLocation[1]-averageLampDistance), -(averageLampLocation[2]-averageLampDistance), averageLampRotation[0], averageLampRotation[1], averageLampRotation[2]))
+## #v(A,B) rotates vector A about origin by vector B.
+##
+####################################################################################################################################
+
+ def exportMeta(metas):
+
+ # TODO - blenders 'motherball' naming is not supported.
+
+ for ob in metas:
+ meta = ob.data
+
+ file.write('blob {\n')
+ file.write('\t\tthreshold %.4g\n' % meta.threshold)
+
+ try:
+ material = meta.materials[0] # lame! - blender cant do enything else.
+ except:
+ material = None
+
+ for elem in meta.elements:
+
+ if elem.type not in ('BALL', 'ELLIPSOID'):
+ continue # Not supported
+
+ loc = elem.co
+
+ stiffness = elem.stiffness
+ if elem.use_negative:
+ stiffness = - stiffness
+
+ if elem.type == 'BALL':
+
+ file.write('\tsphere { <%.6g, %.6g, %.6g>, %.4g, %.4g ' % (loc.x, loc.y, loc.z, elem.radius, stiffness))
+
+ # After this wecould do something simple like...
+ # "pigment {Blue} }"
+ # except we'll write the color
+
+ elif elem.type == 'ELLIPSOID':
+ # location is modified by scale
+ file.write('\tsphere { <%.6g, %.6g, %.6g>, %.4g, %.4g ' % (loc.x / elem.size_x, loc.y / elem.size_y, loc.z / elem.size_z, elem.radius, stiffness))
+ file.write('scale <%.6g, %.6g, %.6g> ' % (elem.size_x, elem.size_y, elem.size_z))
+
+ if material:
+ diffuse_color = material.diffuse_color
+
+ if material.use_transparency and material.transparency_method == 'RAYTRACE':
+ trans = 1.0 - material.raytrace_transparency.filter
+ else:
+ trans = 0.0
+
+ file.write('pigment {rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>} finish {%s} }\n' % \
+ (diffuse_color[0], diffuse_color[1], diffuse_color[2], 1.0 - material.alpha, trans, materialNames[material.name]))
+
+ else:
+ file.write('pigment {rgb<1 1 1>} finish {%s} }\n' % DEF_MAT_NAME) # Write the finish last.
+
+ writeObjectMaterial(material)
+
+ writeMatrix(ob.matrix_world)
+
+ file.write('}\n')
+
+ objectNames = {}
+ DEF_OBJ_NAME = 'Default'
+ def exportMeshs(scene, sel):
+
+ ob_num = 0
+
+ for ob in sel:
+ ob_num += 1
+#############################################
+ #Generating a name for object just like materials to be able to use it (baking for now or anything else).
+ if sel:
+ name_orig = ob.name
+ else:
+ name_orig = DEF_OBJ_NAME
+ name = objectNames[name_orig] = uniqueName(bpy.path.clean_name(name_orig), objectNames)
+#############################################
+ if ob.type in ('LAMP', 'CAMERA', 'EMPTY', 'META', 'ARMATURE', 'LATTICE'):
+ continue
+
+ me = ob.data
+ me_materials = me.materials
+
+ me = ob.create_mesh(scene, True, 'RENDER')
+
+ if not me or not me.faces:
+ continue
+
+ if info_callback:
+ info_callback('Object %2.d of %2.d (%s)' % (ob_num, len(sel), ob.name))
+
+ #if ob.type!='MESH':
+ # continue
+ # me = ob.data
+
+ matrix = ob.matrix_world
+ try:
+ uv_layer = me.uv_textures.active.data
+ except AttributeError:
+ uv_layer = None
+
+ try:
+ vcol_layer = me.vertex_colors.active.data
+ except AttributeError:
+ vcol_layer = None
+
+ faces_verts = [f.vertices[:] for f in me.faces]
+ faces_normals = [tuple(f.normal) for f in me.faces]
+ verts_normals = [tuple(v.normal) for v in me.vertices]
+
+ # quads incur an extra face
+ quadCount = sum(1 for f in faces_verts if len(f) == 4)
+
+ # Use named declaration to allow reference e.g. for baking. MR
+ file.write('#declare %s=\n' % name)
+ file.write('mesh2 {\n')
+ file.write('\tvertex_vectors {\n')
+ file.write('\t\t%s' % (len(me.vertices))) # vert count
+ for v in me.vertices:
+ file.write(',\n\t\t<%.6f, %.6f, %.6f>' % tuple(v.co)) # vert count
+ file.write('\n }\n')
+
+
+ # Build unique Normal list
+ uniqueNormals = {}
+ for fi, f in enumerate(me.faces):
+ fv = faces_verts[fi]
+ # [-1] is a dummy index, use a list so we can modify in place
+ if f.use_smooth: # Use vertex normals
+ for v in fv:
+ key = verts_normals[v]
+ uniqueNormals[key] = [-1]
+ else: # Use face normal
+ key = faces_normals[fi]
+ uniqueNormals[key] = [-1]
+
+ file.write('\tnormal_vectors {\n')
+ file.write('\t\t%d' % len(uniqueNormals)) # vert count
+ idx = 0
+ for no, index in uniqueNormals.items():
+ file.write(',\n\t\t<%.6f, %.6f, %.6f>' % no) # vert count
+ index[0] = idx
+ idx += 1
+ file.write('\n }\n')
+
+
+ # Vertex colours
+ vertCols = {} # Use for material colours also.
+
+ if uv_layer:
+ # Generate unique UV's
+ uniqueUVs = {}
+
+ for fi, uv in enumerate(uv_layer):
+
+ if len(faces_verts[fi]) == 4:
+ uvs = uv.uv1, uv.uv2, uv.uv3, uv.uv4
+ else:
+ uvs = uv.uv1, uv.uv2, uv.uv3
+
+ for uv in uvs:
+ uniqueUVs[tuple(uv)] = [-1]
+
+ file.write('\tuv_vectors {\n')
+ #print unique_uvs
+ file.write('\t\t%s' % (len(uniqueUVs))) # vert count
+ idx = 0
+ for uv, index in uniqueUVs.items():
+ file.write(',\n\t\t<%.6f, %.6f>' % uv)
+ index[0] = idx
+ idx += 1
+ '''
+ else:
+ # Just add 1 dummy vector, no real UV's
+ file.write('\t\t1') # vert count
+ file.write(',\n\t\t<0.0, 0.0>')
+ '''
+ file.write('\n }\n')
+
+
+ if me.vertex_colors:
+
+ for fi, f in enumerate(me.faces):
+ material_index = f.material_index
+ material = me_materials[material_index]
+
+ if material and material.use_vertex_color_paint:
+
+ col = vcol_layer[fi]
+
+ if len(faces_verts[fi]) == 4:
+ cols = col.color1, col.color2, col.color3, col.color4
+ else:
+ cols = col.color1, col.color2, col.color3
+
+ for col in cols:
+ key = col[0], col[1], col[2], material_index # Material index!
+ vertCols[key] = [-1]
+
+ else:
+ if material:
+ diffuse_color = tuple(material.diffuse_color)
+ key = diffuse_color[0], diffuse_color[1], diffuse_color[2], material_index
+ vertCols[key] = [-1]
+
+
+ else:
+ # No vertex colours, so write material colours as vertex colours
+ for i, material in enumerate(me_materials):
+
+ if material:
+ diffuse_color = tuple(material.diffuse_color)
+ key = diffuse_color[0], diffuse_color[1], diffuse_color[2], i # i == f.mat
+ vertCols[key] = [-1]
+
+
+ # Vert Colours
+ file.write('\ttexture_list {\n')
+ file.write('\t\t%s' % (len(vertCols))) # vert count
+ idx = 0
+ for col, index in vertCols.items():
+
+ if me_materials:
+ material = me_materials[col[3]]
+ material_finish = materialNames[material.name]
+
+ if material.use_transparency:
+ trans = 1.0 - material.alpha
+ else:
+ trans = 0.0
+
+ else:
+ material_finish = DEF_MAT_NAME # not working properly,
+ trans = 0.0
+
+ ##############SF
+ texturesDif=''
+ texturesSpec=''
+ texturesNorm=''
+ texturesAlpha=''
+ for t in material.texture_slots:
+ if t and t.texture.type == 'IMAGE' and t.use and t.texture.image:
+ image_filename = path_image(t.texture.image.filepath)
+ if image_filename:
+ if t.use_map_color_diffuse:
+ texturesDif = image_filename
+ colvalue = t.default_value
+ t_dif = t
+ if t.use_map_specular or t.use_map_raymir:
+ texturesSpec = image_filename
+ colvalue = t.default_value
+ t_spec = t
+ if t.use_map_normal:
+ texturesNorm = image_filename
+ colvalue = t.normal_factor * 10.0
+ #textNormName=t.texture.image.name + '.normal'
+ #was the above used? --MR
+ t_nor = t
+ if t.use_map_alpha:
+ texturesAlpha = image_filename
+ colvalue = t.alpha_factor * 10.0
+ #textDispName=t.texture.image.name + '.displ'
+ #was the above used? --MR
+ t_alpha = t
+
+
+
+
+ ##############################################################################################################
+ file.write('\n\t\ttexture {') #THIS AREA NEEDS TO LEAVE THE TEXTURE OPEN UNTIL ALL MAPS ARE WRITTEN DOWN. --MR
+
+
+ ##############################################################################################################
+ if material.diffuse_shader == 'MINNAERT':
+ file.write('\n\t\t\taoi')
+ file.write('\n\t\t\ttexture_map {')
+ file.write('\n\t\t\t\t[%.3g finish {diffuse %.3g}]' % ((material.darkness/2), (2-material.darkness)))
+ file.write('\n\t\t\t\t[%.3g' % (1-(material.darkness/2)))
+######TO OPTIMIZE? or present a more elegant way? At least make it work!##################################################################
+ #If Fresnel gets removed from 2.5, why bother?
+ if material.diffuse_shader == 'FRESNEL':
+
+######END of part TO OPTIMIZE? or present a more elegant way?##################################################################
+
+## #lampLocation=lamp.position
+## lampRotation=
+## a=lamp.Rotation[0]
+## b=lamp.Rotation[1]
+## c=lamp.Rotation[2]
+## lampLookAt=tuple (x,y,z)
+## lampLookAt[3]= 0.0 #Put "target" of the lamp on the floor plane to elimianate one unknown value
+## degrees(atan((lampLocation - lampLookAt).y/(lampLocation - lampLookAt).z))=lamp.rotation[0]
+## degrees(atan((lampLocation - lampLookAt).z/(lampLocation - lampLookAt).x))=lamp.rotation[1]
+## degrees(atan((lampLocation - lampLookAt).x/(lampLocation - lampLookAt).y))=lamp.rotation[2]
+## degrees(atan((lampLocation - lampLookAt).y/(lampLocation.z))=lamp.rotation[0]
+## degrees(atan((lampLocation.z/(lampLocation - lampLookAt).x))=lamp.rotation[1]
+## degrees(atan((lampLocation - lampLookAt).x/(lampLocation - lampLookAt).y))=lamp.rotation[2]
+
+
+ #color = tuple([c * lamp.energy for c in lamp.color]) # Colour is modified by energy
+
+
+ file.write('\n\t\t\tslope { lampTarget }')
+ file.write('\n\t\t\ttexture_map {')
+ file.write('\n\t\t\t\t[%.3g finish {diffuse %.3g}]' % ((material.diffuse_fresnel/2), (2-material.diffuse_fresnel_factor)))
+ file.write('\n\t\t\t\t[%.3g' % (1-(material.diffuse_fresnel/2)))
+
+
+ #if material.diffuse_shader == 'FRESNEL': pigment pattern aoi pigment and texture map above, the rest below as one of its entry
+ ##########################################################################################################################
+ if texturesSpec !='':
+ file.write('\n\t\t\t\tpigment_pattern {')
+ mappingSpec = (" translate <%.4g-0.75,%.4g-0.75,%.4g-0.75> scale <%.4g,%.4g,%.4g>" % (t_spec.offset.x / 10 ,t_spec.offset.y / 10 ,t_spec.offset.z / 10, t_spec.scale.x / 2.25, t_spec.scale.y / 2.25, t_spec.scale.z / 2.25)) #strange that the translation factor for scale is not the same as for translate. ToDo: verify both matches with blender internal.
+ file.write('\n\t\t\t\t\tuv_mapping image_map{%s \"%s\" %s}%s}' % (imageFormat(texturesSpec) ,texturesSpec ,imgMap(t_spec),mappingSpec))
+ file.write('\n\t\t\t\t\t\ttexture_map {')
+ file.write('\n\t\t\t\t\t\t\t[0 ')
+
+ if texturesDif == '':
+ if texturesAlpha !='':
+ mappingAlpha = (" translate <%.4g-0.75,%.4g-0.75,%.4g-0.75> scale <%.4g,%.4g,%.4g>" % (t_alpha.offset.x / 10 ,t_alpha.offset.y / 10 ,t_alpha.offset.z / 10, t_alpha.scale.x / 2.25, t_alpha.scale.y / 2.25, t_alpha.scale.z / 2.25)) #strange that the translation factor for scale is not the same as for translate. ToDo: verify both matches with blender internal.
+ file.write('\n\t\t\t\tpigment {pigment_pattern {uv_mapping image_map{%s \"%s\" %s}%s}' % (imageFormat(texturesAlpha) ,texturesAlpha ,imgMap(t_alpha),mappingAlpha))
+ file.write('\n\t\t\t\t\tpigment_map {')
+ file.write('\n\t\t\t\t\t\t[0 color rgbft<0,0,0,1,1>]')
+ file.write('\n\t\t\t\t\t\t[1 color rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>]\n\t\t\t\t\t}' % (col[0], col[1], col[2], 1.0 - material.alpha, trans))
+ file.write('\n\t\t\t\t}')
+
+ else:
+
+ file.write('\n\t\t\t\tpigment {rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>}' % (col[0], col[1], col[2], 1.0 - material.alpha, trans))
+
+ if texturesSpec !='':
+ file.write('finish {%s}' % (safety(material_finish, Level=1)))# Level 1 is no specular
+
+ else:
+ file.write('finish {%s}' % (safety(material_finish, Level=2)))# Level 2 is translated spec
+
+ else:
+ mappingDif = (" translate <%.4g-0.75,%.4g-0.75,%.4g-0.75> scale <%.4g,%.4g,%.4g>" % (t_dif.offset.x / 10 ,t_dif.offset.y / 10 ,t_dif.offset.z / 10, t_dif.scale.x / 2.25, t_dif.scale.y / 2.25, t_dif.scale.z / 2.25)) #strange that the translation factor for scale is not the same as for translate. ToDo: verify both matches with blender internal.
+
+ if texturesAlpha !='':
+ mappingAlpha = (" translate <%.4g-0.75,%.4g-0.75,%.4g-0.75> scale <%.4g,%.4g,%.4g>" % (t_alpha.offset.x / 10 ,t_alpha.offset.y / 10 ,t_alpha.offset.z / 10, t_alpha.scale.x / 2.25, t_alpha.scale.y / 2.25, t_alpha.scale.z / 2.25)) #strange that the translation factor for scale is not the same as for translate. ToDo: verify both matches with blender internal.
+ file.write('\n\t\t\t\tpigment {pigment_pattern {uv_mapping image_map{%s \"%s\" %s}%s}' % (imageFormat(texturesAlpha),texturesAlpha,imgMap(t_alpha),mappingAlpha))
+ file.write('\n\t\t\t\t\tpigment_map {\n\t\t\t\t\t\t[0 color rgbft<0,0,0,1,1>]')
+ file.write('\n\t\t\t\t\t\t[1 uv_mapping image_map {%s \"%s\" %s}%s]\n\t\t\t\t}' % (imageFormat(texturesDif),texturesDif,imgMap(t_dif),mappingDif))
+ file.write('\n\t\t\t\t}')
+
+ else:
+ file.write("\n\t\t\t\tpigment {uv_mapping image_map {%s \"%s\" %s}%s}" % (imageFormat(texturesDif),texturesDif,imgMap(t_dif),mappingDif))
+
+ if texturesSpec !='':
+ file.write('finish {%s}' % (safety(material_finish, Level=1)))# Level 1 is no specular
+
+ else:
+ file.write('finish {%s}' % (safety(material_finish, Level=2)))# Level 2 is translated specular
+
+ ## scale 1 rotate y*0
+ #imageMap = ("{image_map {%s \"%s\" %s }" % (imageFormat(textures),textures,imgMap(t_dif)))
+ #file.write("\n\t\t\tuv_mapping pigment %s} %s finish {%s}" % (imageMap,mapping,safety(material_finish)))
+ #file.write("\n\t\t\tpigment {uv_mapping image_map {%s \"%s\" %s}%s} finish {%s}" % (imageFormat(texturesDif),texturesDif,imgMap(t_dif),mappingDif,safety(material_finish)))
+ if texturesNorm !='':
+ ## scale 1 rotate y*0
+ mappingNor = (" translate <%.4g-0.75,%.4g-0.75,%.4g-0.75> scale <%.4g,%.4g,%.4g>" % (t_nor.offset.x / 10 ,t_nor.offset.y / 10 ,t_nor.offset.z / 10, t_nor.scale.x / 2.25, t_nor.scale.y / 2.25, t_nor.scale.z / 2.25))
+ #imageMapNor = ("{bump_map {%s \"%s\" %s mapping}" % (imageFormat(texturesNorm),texturesNorm,imgMap(t_nor)))
+ #We were not using the above maybe we should?
+ file.write("\n\t\t\t\tnormal {uv_mapping bump_map {%s \"%s\" %s bump_size %.4g }%s}" % (imageFormat(texturesNorm),texturesNorm,imgMap(t_nor),(t_nor.normal_factor * 10),mappingNor))
+ if texturesSpec !='':
+ file.write('\n\t\t\t\t\t\t\t]')
+ ################################Second index for mapping specular max value##################################################################################################
+ file.write('\n\t\t\t\t\t\t\t[1 ')
+
+ if texturesDif == '':
+ if texturesAlpha !='':
+ mappingAlpha = (" translate <%.4g-0.75,%.4g-0.75,%.4g-0.75> scale <%.4g,%.4g,%.4g>" % (t_alpha.offset.x / 10 ,t_alpha.offset.y / 10 ,t_alpha.offset.z / 10, t_alpha.scale.x / 2.25, t_alpha.scale.y / 2.25, t_alpha.scale.z / 2.25)) #strange that the translation factor for scale is not the same as for translate. ToDo: verify both matches with blender internal.
+ file.write('\n\t\t\t\tpigment {pigment_pattern {uv_mapping image_map{%s \"%s\" %s}%s}' % (imageFormat(texturesAlpha) ,texturesAlpha ,imgMap(t_alpha),mappingAlpha))
+ file.write('\n\t\t\t\t\tpigment_map {')
+ file.write('\n\t\t\t\t\t\t[0 color rgbft<0,0,0,1,1>]')
+ file.write('\n\t\t\t\t\t\t[1 color rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>]\n\t\t\t\t\t}' % (col[0], col[1], col[2], 1.0 - material.alpha, trans))
+ file.write('\n\t\t\t\t}')
+
+ else:
+ file.write('\n\t\t\t\tpigment {rgbft<%.3g, %.3g, %.3g, %.3g, %.3g>}' % (col[0], col[1], col[2], 1.0 - material.alpha, trans))
+
+ if texturesSpec !='':
+ file.write('finish {%s}' % (safety(material_finish, Level=3)))# Level 3 is full specular
+
+ else:
+ file.write('finish {%s}' % (safety(material_finish, Level=2)))# Level 2 is translated specular
+
+ else:
+ mappingDif = (" translate <%.4g-0.75,%.4g-0.75,%.4g-0.75> scale <%.4g,%.4g,%.4g>" % (t_dif.offset.x / 10 ,t_dif.offset.y / 10 ,t_dif.offset.z / 10, t_dif.scale.x / 2.25, t_dif.scale.y / 2.25, t_dif.scale.z / 2.25)) #strange that the translation factor for scale is not the same as for translate. ToDo: verify both matches with blender internal.
+
+ if texturesAlpha !='':
+ mappingAlpha = (" translate <%.4g-0.75,%.4g-0.75,%.4g-0.75> scale <%.4g,%.4g,%.4g>" % (t_alpha.offset.x / 10 ,t_alpha.offset.y / 10 ,t_alpha.offset.z / 10, t_alpha.scale.x / 2.25, t_alpha.scale.y / 2.25, t_alpha.scale.z / 2.25)) #strange that the translation factor for scale is not the same as for translate. ToDo: verify both matches with blender internal.
+ file.write('\n\t\t\t\tpigment {pigment_pattern {uv_mapping image_map{%s \"%s\" %s}%s}' % (imageFormat(texturesAlpha),texturesAlpha,imgMap(t_alpha),mappingAlpha))
+ file.write('\n\t\t\t\tpigment_map {\n\t\t\t\t\t[0 color rgbft<0,0,0,1,1>]')
+ file.write('\n\t\t\t\t\t\t[1 uv_mapping image_map {%s \"%s\" %s}%s]\n\t\t\t\t\t}' % (imageFormat(texturesDif),texturesDif,imgMap(t_dif),mappingDif))
+ file.write('\n\t\t\t\t}')
+
+ else:
+ file.write("\n\t\t\tpigment {uv_mapping image_map {%s \"%s\" %s}%s}" % (imageFormat(texturesDif),texturesDif,imgMap(t_dif),mappingDif))
+ if texturesSpec !='':
+ file.write('finish {%s}' % (safety(material_finish, Level=3)))# Level 3 is full specular
+ else:
+ file.write('finish {%s}' % (safety(material_finish, Level=2)))# Level 2 is translated specular
+
+ ## scale 1 rotate y*0
+ #imageMap = ("{image_map {%s \"%s\" %s }" % (imageFormat(textures),textures,imgMap(t_dif)))
+ #file.write("\n\t\t\tuv_mapping pigment %s} %s finish {%s}" % (imageMap,mapping,safety(material_finish)))
+ #file.write("\n\t\t\tpigment {uv_mapping image_map {%s \"%s\" %s}%s} finish {%s}" % (imageFormat(texturesDif),texturesDif,imgMap(t_dif),mappingDif,safety(material_finish)))
+ if texturesNorm !='':
+ ## scale 1 rotate y*0
+ mappingNor = (" translate <%.4g-0.75,%.4g-0.75,%.4g-0.75> scale <%.4g,%.4g,%.4g>" % (t_nor.offset.x / 10 ,t_nor.offset.y / 10 ,t_nor.offset.z / 10, t_nor.scale.x / 2.25, t_nor.scale.y / 2.25, t_nor.scale.z / 2.25))
+ #imageMapNor = ("{bump_map {%s \"%s\" %s mapping}" % (imageFormat(texturesNorm),texturesNorm,imgMap(t_nor)))
+ #We were not using the above maybe we should?
+ file.write("\n\t\t\t\tnormal {uv_mapping bump_map {%s \"%s\" %s bump_size %.4g }%s}" % (imageFormat(texturesNorm),texturesNorm,imgMap(t_nor),(t_nor.normal_factor * 10),mappingNor))
+ if texturesSpec !='':
+ file.write('\n\t\t\t\t\t\t\t]')
+
+ file.write('\n\t\t\t\t}')
+
+ #End of slope/ior texture_map
+ if material.diffuse_shader == 'MINNAERT' or material.diffuse_shader == 'FRESNEL':
+ file.write('\n\t\t\t\t]')
+ file.write('\n\t\t\t}')
+ file.write('\n\t\t}') #THEN IT CAN CLOSE IT --MR
+
+
+ ############################################################################################################
+
+ index[0] = idx
+ idx += 1
+
+ file.write('\n\t}\n')
+
+ # Face indicies
+ file.write('\tface_indices {\n')
+ file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count
+ for fi, f in enumerate(me.faces):
+ fv = faces_verts[fi]
+ material_index = f.material_index
+ if len(fv) == 4:
+ indicies = (0, 1, 2), (0, 2, 3)
+ else:
+ indicies = ((0, 1, 2),)
+
+ if vcol_layer:
+ col = vcol_layer[fi]
+
+ if len(fv) == 4:
+ cols = col.color1, col.color2, col.color3, col.color4
+ else:
+ cols = col.color1, col.color2, col.color3
+
+
+ if not me_materials or me_materials[material_index] is None: # No materials
+ for i1, i2, i3 in indicies:
+ file.write(',\n\t\t<%d,%d,%d>' % (fv[i1], fv[i2], fv[i3])) # vert count
+ else:
+ material = me_materials[material_index]
+ for i1, i2, i3 in indicies:
+ if me.vertex_colors and material.use_vertex_color_paint:
+ # Colour per vertex - vertex colour
+
+ col1 = cols[i1]
+ col2 = cols[i2]
+ col3 = cols[i3]
+
+ ci1 = vertCols[col1[0], col1[1], col1[2], material_index][0]
+ ci2 = vertCols[col2[0], col2[1], col2[2], material_index][0]
+ ci3 = vertCols[col3[0], col3[1], col3[2], material_index][0]
+ else:
+ # Colour per material - flat material colour
+ diffuse_color = material.diffuse_color
+ ci1 = ci2 = ci3 = vertCols[diffuse_color[0], diffuse_color[1], diffuse_color[2], f.material_index][0]
+
+ file.write(',\n\t\t<%d,%d,%d>, %d,%d,%d' % (fv[i1], fv[i2], fv[i3], ci1, ci2, ci3)) # vert count
+
+
+ file.write('\n }\n')
+
+ # normal_indices indicies
+ file.write('\tnormal_indices {\n')
+ file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count
+ for fi, fv in enumerate(faces_verts):
+
+ if len(fv) == 4:
+ indicies = (0, 1, 2), (0, 2, 3)
+ else:
+ indicies = ((0, 1, 2),)
+
+ for i1, i2, i3 in indicies:
+ if f.use_smooth:
+ file.write(',\n\t\t<%d,%d,%d>' %\
+ (uniqueNormals[verts_normals[fv[i1]]][0],\
+ uniqueNormals[verts_normals[fv[i2]]][0],\
+ uniqueNormals[verts_normals[fv[i3]]][0])) # vert count
+ else:
+ idx = uniqueNormals[faces_normals[fi]][0]
+ file.write(',\n\t\t<%d,%d,%d>' % (idx, idx, idx)) # vert count
+
+ file.write('\n }\n')
+
+ if uv_layer:
+ file.write('\tuv_indices {\n')
+ file.write('\t\t%d' % (len(me.faces) + quadCount)) # faces count
+ for fi, fv in enumerate(faces_verts):
+
+ if len(fv) == 4:
+ indicies = (0, 1, 2), (0, 2, 3)
+ else:
+ indicies = ((0, 1, 2),)
+
+ uv = uv_layer[fi]
+ if len(faces_verts[fi]) == 4:
+ uvs = tuple(uv.uv1), tuple(uv.uv2), tuple(uv.uv3), tuple(uv.uv4)
+ else:
+ uvs = tuple(uv.uv1), tuple(uv.uv2), tuple(uv.uv3)
+
+ for i1, i2, i3 in indicies:
+ file.write(',\n\t\t<%d,%d,%d>' %\
+ (uniqueUVs[uvs[i1]][0],\
+ uniqueUVs[uvs[i2]][0],\
+ uniqueUVs[uvs[i3]][0]))
+ file.write('\n }\n')
+
+ if me.materials:
+ try:
+ material = me.materials[0] # dodgy
+ writeObjectMaterial(material)
+ except IndexError:
+ print(me)
+
+ writeMatrix(matrix)
+ file.write('}\n')
+ file.write('%s\n' % name) # Use named declaration to allow reference e.g. for baking. MR
+
+ bpy.data.meshes.remove(me)
+
+ def exportWorld(world):
+ render = scene.render
+ camera = scene.camera
+ matrix = camera.matrix_world
+ if not world:
+ return
+ #############Maurice####################################
+ #These lines added to get sky gradient (visible with PNG output)
+ if world:
+ #For simple flat background:
+ if not world.use_sky_blend:
+ #Non fully transparent background could premultiply alpha and avoid anti-aliasing display issue:
+ if render.alpha_mode == 'PREMUL' or render.alpha_mode == 'PREMUL' :
+ file.write('background {rgbt<%.3g, %.3g, %.3g, 0.75>}\n' % (tuple(world.horizon_color)))
+ #Currently using no alpha with Sky option:
+ elif render.alpha_mode == 'SKY':
+ file.write('background {rgbt<%.3g, %.3g, %.3g, 0>}\n' % (tuple(world.horizon_color)))
+ #StraightAlpha:
+ else:
+ file.write('background {rgbt<%.3g, %.3g, %.3g, 1>}\n' % (tuple(world.horizon_color)))
+
+
+
+ #For Background image textures
+ for t in world.texture_slots: #risk to write several sky_spheres but maybe ok.
+ if t and t.texture.type == 'IMAGE': #and t.use: #No enable checkbox for world textures yet (report it?)
+ image_filename = path_image(t.texture.image.filepath)
+ if t.texture.image.filepath != image_filename: t.texture.image.filepath = image_filename
+ if image_filename != '' and t.use_map_blend:
+ texturesBlend = image_filename
+ #colvalue = t.default_value
+ t_blend = t
+ #commented below was an idea to make the Background image oriented as camera taken here: http://news.povray.org/povray.newusers/thread/%3Cweb.4a5cddf4e9c9822ba2f93e20@news.povray.org%3E/
+ #mappingBlend = (" translate <%.4g,%.4g,%.4g> rotate z*degrees(atan((camLocation - camLookAt).x/(camLocation - camLookAt).y)) rotate x*degrees(atan((camLocation - camLookAt).y/(camLocation - camLookAt).z)) rotate y*degrees(atan((camLocation - camLookAt).z/(camLocation - camLookAt).x)) scale <%.4g,%.4g,%.4g>b" % (t_blend.offset.x / 10 ,t_blend.offset.y / 10 ,t_blend.offset.z / 10, t_blend.scale.x ,t_blend.scale.y ,t_blend.scale.z))#replace 4/3 by the ratio of each image found by some custom or existing function
+ #using camera rotation valuesdirectly from blender seems much easier
+ mappingBlend = (" translate <%.4g-0.5,%.4g-0.5,%.4g-0.5> rotate<%.4g,%.4g,%.4g> scale <%.4g,%.4g,%.4g>" % (t_blend.offset.x / 10 ,t_blend.offset.y / 10 ,t_blend.offset.z / 10, degrees(camera.rotation_euler[0]), degrees(camera.rotation_euler[1]), degrees(camera.rotation_euler[2]), t_blend.scale.x*0.85 , t_blend.scale.y*0.85 , t_blend.scale.z*0.85 ))
+ #Putting the map on a plane would not introduce the skysphere distortion and allow for better image scale matching but also some waay to chose depth and size of the plane relative to camera.
+ file.write('sky_sphere {\n')
+ file.write('\tpigment {\n')
+ file.write("\t\timage_map{%s \"%s\" %s}\n\t}\n\t%s\n" % (imageFormat(texturesBlend),texturesBlend,imgMapBG(t_blend),mappingBlend))
+ file.write('}\n')
+ #file.write('\t\tscale 2\n')
+ #file.write('\t\ttranslate -1\n')
+
+ #For only Background gradient
+
+ if not t:
+ if world.use_sky_blend:
+ file.write('sky_sphere {\n')
+ file.write('\tpigment {\n')
+ file.write('\t\tgradient z\n')#maybe Should follow the advice of POV doc about replacing gradient for skysphere..5.5
+ file.write('\t\tcolor_map {\n')
+ if render.alpha_mode == 'STRAIGHT':
+ file.write('\t\t\t[0.0 rgbt<%.3g, %.3g, %.3g, 1>]\n' % (tuple(world.horizon_color)))
+ file.write('\t\t\t[1.0 rgbt<%.3g, %.3g, %.3g, 1>]\n' % (tuple(world.zenith_color)))
+ elif render.alpha_mode == 'PREMUL':
+ file.write('\t\t\t[0.0 rgbt<%.3g, %.3g, %.3g, 0.99>]\n' % (tuple(world.horizon_color)))
+ file.write('\t\t\t[1.0 rgbt<%.3g, %.3g, %.3g, 0.99>]\n' % (tuple(world.zenith_color))) #aa premult not solved with transmit 1
+ else:
+ file.write('\t\t\t[0.0 rgbt<%.3g, %.3g, %.3g, 0>]\n' % (tuple(world.horizon_color)))
+ file.write('\t\t\t[1.0 rgbt<%.3g, %.3g, %.3g, 0>]\n' % (tuple(world.zenith_color)))
+ file.write('\t\t}\n')
+ file.write('\t}\n')
+ file.write('}\n')
+ #sky_sphere alpha (transmit) is not translating into image alpha the same way as "background"
+
+ if world.light_settings.use_indirect_light:
+ scene.pov_radio_enable=1
+
+ #Maybe change the above to scene.pov_radio_enable = world.light_settings.use_indirect_light ?
+
+
+ ###############################################################
+
+ mist = world.mist_settings
+
+ if mist.use_mist:
+ file.write('fog {\n')
+ file.write('\tdistance %.6f\n' % mist.depth)
+ file.write('\tcolor rgbt<%.3g, %.3g, %.3g, %.3g>\n' % (tuple(world.horizon_color) + (1 - mist.intensity,)))
+ #file.write('\tfog_offset %.6f\n' % mist.start)
+ #file.write('\tfog_alt 5\n')
+ #file.write('\tturbulence 0.2\n')
+ #file.write('\tturb_depth 0.3\n')
+ file.write('\tfog_type 1\n')
+ file.write('}\n')
+
+ def exportGlobalSettings(scene):
+
+ file.write('global_settings {\n')
+ file.write('\tmax_trace_level 7\n')
+
+ if scene.pov_radio_enable:
+ file.write('\tradiosity {\n')
+ file.write("\t\tadc_bailout %.4g\n" % scene.pov_radio_adc_bailout)
+ file.write("\t\talways_sample %d\n" % scene.pov_radio_always_sample)
+ file.write("\t\tbrightness %.4g\n" % scene.pov_radio_brightness)
+ file.write("\t\tcount %d\n" % scene.pov_radio_count)
+ file.write("\t\terror_bound %.4g\n" % scene.pov_radio_error_bound)
+ file.write("\t\tgray_threshold %.4g\n" % scene.pov_radio_gray_threshold)
+ file.write("\t\tlow_error_factor %.4g\n" % scene.pov_radio_low_error_factor)
+ file.write("\t\tmedia %d\n" % scene.pov_radio_media)
+ file.write("\t\tminimum_reuse %.4g\n" % scene.pov_radio_minimum_reuse)
+ file.write("\t\tnearest_count %d\n" % scene.pov_radio_nearest_count)
+ file.write("\t\tnormal %d\n" % scene.pov_radio_normal)
+ file.write("\t\trecursion_limit %d\n" % scene.pov_radio_recursion_limit)
+ file.write('\t}\n')
+ once=1
+ for material in bpy.data.materials:
+ if material.subsurface_scattering.use and once:
+ file.write("\tmm_per_unit %.6f\n" % (material.subsurface_scattering.scale * (-100) + 15))#In pov, the scale has reversed influence compared to blender. these number should correct that
+ once=0 #In povray, the scale factor for all subsurface shaders needs to be the same
+
+ if world:
+ file.write("\tambient_light rgb<%.3g, %.3g, %.3g>\n" % tuple(world.ambient_color))
+
+ if material.pov_photons_refraction or material.pov_photons_reflection:
+ file.write("\tphotons {\n")
+ file.write("\t\tspacing 0.003\n")
+ file.write("\t\tmax_trace_level 4\n")
+ file.write("\t\tadc_bailout 0.1\n")
+ file.write("\t\tgather 30, 150\n")
+
+
+ file.write("\t}\n")
+
+ file.write('}\n')
+
+
+ # Convert all materials to strings we can access directly per vertex.
+ writeMaterial(None) # default material
+
+ for material in bpy.data.materials:
+ writeMaterial(material)
+
+
+ #exportMaterials()
+ sel = scene.objects
+ exportLamps([l for l in sel if l.type == 'LAMP'])
+ exportMeta([l for l in sel if l.type == 'META'])
+ exportMeshs(scene, sel)
+ exportCamera()
+ exportWorld(scene.world)
+ exportGlobalSettings(scene)
+
+ file.close()
+
+
+
+def write_pov_ini(filename_ini, filename_pov, filename_image):
+ scene = bpy.data.scenes[0]
+ render = scene.render
+
+ x = int(render.resolution_x * render.resolution_percentage * 0.01)
+ y = int(render.resolution_y * render.resolution_percentage * 0.01)
+
+ file = open(filename_ini, 'w')
+ file.write('Input_File_Name="%s"\n' % filename_pov)
+ file.write('Output_File_Name="%s"\n' % filename_image)
+
+ file.write('Width=%d\n' % x)
+ file.write('Height=%d\n' % y)
+
+ # Needed for border render.
+ '''
+ file.write('Start_Column=%d\n' % part.x)
+ file.write('End_Column=%d\n' % (part.x+part.w))
+
+ file.write('Start_Row=%d\n' % (part.y))
+ file.write('End_Row=%d\n' % (part.y+part.h))
+ '''
+
+ file.write('Bounding_method=2\n')#The new automatic BSP is faster in most scenes
+
+ file.write('Display=1\n')#Activated (turn this back off when better live exchange is done between the two programs (see next comment)
+ file.write('Pause_When_Done=0\n')
+ file.write('Output_File_Type=N\n') # PNG, with POV 3.7, can show background color with alpha. In the long run using the Povray interactive preview like bishop 3D could solve the preview for all formats.
+ #file.write('Output_File_Type=T\n') # TGA, best progressive loading
+ file.write('Output_Alpha=1\n')
+
+ if render.use_antialiasing:
+ aa_mapping = {'5': 2, '8': 3, '11': 4, '16': 5} # method 2 (recursive) with higher max subdiv forced because no mipmapping in povray needs higher sampling.
+ file.write('Antialias=1\n')
+ file.write('Sampling_Method=2n')
+ file.write('Antialias_Depth=%d\n' % aa_mapping[render.antialiasing_samples])
+ file.write('Antialias_Threshold=0.1\n')#rather high settings but necessary.
+ file.write('Jitter=off\n')#prevent animation flicker
+
+ else:
+ file.write('Antialias=0\n')
+ file.write('Version=3.7')
+ file.close()
+
+
+class PovrayRender(bpy.types.RenderEngine):
+ bl_idname = 'POVRAY_RENDER'
+ bl_label = "Povray 3.7"
+ DELAY = 0.05
+
+ def _export(self, scene):
+ import tempfile
+
+ self._temp_file_in = tempfile.mktemp(suffix='.pov')
+ self._temp_file_out = tempfile.mktemp(suffix='.png')#PNG with POV 3.7, can show the background color with alpha. In the long run using the Povray interactive preview like bishop 3D could solve the preview for all formats.
+ #self._temp_file_out = tempfile.mktemp(suffix='.tga')
+ self._temp_file_ini = tempfile.mktemp(suffix='.ini')
+ '''
+ self._temp_file_in = '/test.pov'
+ self._temp_file_out = '/test.png'#PNG with POV 3.7, can show the background color with alpha. In the long run using the Povray interactive preview like bishop 3D could solve the preview for all formats.
+ #self._temp_file_out = '/test.tga'
+ self._temp_file_ini = '/test.ini'
+ '''
+
+ def info_callback(txt):
+ self.update_stats("", "POVRAY 3.7: " + txt)
+
+ write_pov(self._temp_file_in, scene, info_callback)
+
+ def _render(self):
+
+ try:
+ os.remove(self._temp_file_out) # so as not to load the old file
+ except OSError:
+ pass
+
+ write_pov_ini(self._temp_file_ini, self._temp_file_in, self._temp_file_out)
+
+ print ("***-STARTING-***")
+
+ pov_binary = "povray"
+
+ extra_args = []
+
+ if sys.platform == 'win32':
+ import winreg
+ regKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\POV-Ray\\v3.7\\Windows')
+
+ if bitness == 64:
+ pov_binary = winreg.QueryValueEx(regKey, 'Home')[0] + '\\bin\\pvengine64'
+ else:
+ pov_binary = winreg.QueryValueEx(regKey, 'Home')[0] + '\\bin\\pvengine'
+ else:
+ # DH - added -d option to prevent render window popup which leads to segfault on linux
+ extra_args.append("-d")
+
+ if 1:
+ # TODO, when povray isnt found this gives a cryptic error, would be nice to be able to detect if it exists
+ try:
+ self._process = subprocess.Popen([pov_binary, self._temp_file_ini] + extra_args) # stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ except OSError:
+ # TODO, report api
+ print("POVRAY 3.7: could not execute '%s', possibly povray isn't installed" % pov_binary)
+ import traceback
+ traceback.print_exc()
+ print ("***-DONE-***")
+ return False
+
+ else:
+ # This works too but means we have to wait until its done
+ os.system('%s %s' % (pov_binary, self._temp_file_ini))
+
+ # print ("***-DONE-***")
+ return True
+
+ def _cleanup(self):
+ for f in (self._temp_file_in, self._temp_file_ini, self._temp_file_out):
+ try:
+ os.remove(f)
+ except OSError: #was that the proper error type?
+ pass
+
+ self.update_stats("", "")
+
+ def render(self, scene):
+
+ self.update_stats("", "POVRAY 3.7: Exporting data from Blender")
+ self._export(scene)
+ self.update_stats("", "POVRAY 3.7: Parsing File")
+
+ if not self._render():
+ self.update_stats("", "POVRAY 3.7: Not found")
+ return
+
+ r = scene.render
+##WIP output format
+## if r.file_format == 'OPENEXR':
+## fformat = 'EXR'
+## render.color_mode = 'RGBA'
+## else:
+## fformat = 'TGA'
+## r.file_format = 'TARGA'
+## r.color_mode = 'RGBA'
+
+ # compute resolution
+ x = int(r.resolution_x * r.resolution_percentage * 0.01)
+ y = int(r.resolution_y * r.resolution_percentage * 0.01)
+
+ # Wait for the file to be created
+ while not os.path.exists(self._temp_file_out):
+ # print("***POV WAITING FOR FILE***")
+ if self.test_break():
+ try:
+ self._process.terminate()
+ print("***POV INTERRUPTED***")
+ except OSError:
+ pass
+ break
+
+ poll_result = self._process.poll()
+ if poll_result is not None:
+ print("***POV PROCESS FAILED : %s ***" % poll_result)
+ self.update_stats("", "POVRAY 3.7: Failed")
+ break
+
+ time.sleep(self.DELAY)
+
+ if os.path.exists(self._temp_file_out):
+ # print("***POV FILE OK***")
+ self.update_stats("", "POVRAY 3.7: Rendering")
+
+ prev_size = -1
+
+ def update_image():
+ # print("***POV UPDATING IMAGE***")
+ result = self.begin_result(0, 0, x, y)
+ lay = result.layers[0]
+ # possible the image wont load early on.
+ try:
+ lay.load_from_file(self._temp_file_out)
+ except SystemError:
+ pass
+ self.end_result(result)
+
+ # Update while povray renders
+ while True:
+ # print("***POV RENDER LOOP***")
+
+ # test if povray exists
+ if self._process.poll() is not None:
+ print("***POV PROCESS FINISHED***")
+ update_image()
+ break
+
+ # user exit
+ if self.test_break():
+ try:
+ self._process.terminate()
+ print("***POV PROCESS INTERRUPTED***")
+ except OSError:
+ pass
+
+ break
+
+ # Would be nice to redirect the output
+ # stdout_value, stderr_value = self._process.communicate() # locks
+
+
+ # check if the file updated
+ new_size = os.path.getsize(self._temp_file_out)
+
+ if new_size != prev_size:
+ update_image()
+ prev_size = new_size
+
+ time.sleep(self.DELAY)
+ else:
+ print("***POV FILE NOT FOUND***")
+
+ print("***POV FINISHED***")
+ self._cleanup()
+
+
diff --git a/render_povray/ui.py b/render_povray/ui.py
new file mode 100644
index 00000000..d1c0b769
--- /dev/null
+++ b/render_povray/ui.py
@@ -0,0 +1,307 @@
+# ##### 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
+
+# Use some of the existing buttons.
+import properties_render
+properties_render.RENDER_PT_render.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_render.RENDER_PT_dimensions.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_render.RENDER_PT_antialiasing.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_render.RENDER_PT_shading.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_render.RENDER_PT_output.COMPAT_ENGINES.add('POVRAY_RENDER')
+del properties_render
+
+# Use only a subset of the world panels
+import properties_world
+properties_world.WORLD_PT_preview.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_world.WORLD_PT_context_world.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_world.WORLD_PT_world.COMPAT_ENGINES.add('POVRAY_RENDER')
+properties_world.WORLD_PT_mist.COMPAT_ENGINES.add('POVRAY_RENDER')
+del properties_world
+
+# Example of wrapping every class 'as is'
+import properties_material
+for member in dir(properties_material):
+ subclass = getattr(properties_material, member)
+ try:
+ subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+ except:
+ pass
+del properties_material
+
+import properties_data_mesh
+for member in dir(properties_data_mesh):
+ subclass = getattr(properties_data_mesh, member)
+ try:
+ subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+ except:
+ pass
+del properties_data_mesh
+
+import properties_texture
+for member in dir(properties_texture):
+ subclass = getattr(properties_texture, member)
+ try:
+ subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+ except:
+ pass
+del properties_texture
+
+import properties_data_camera
+for member in dir(properties_data_camera):
+ subclass = getattr(properties_data_camera, member)
+ try:
+ subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+ except:
+ pass
+del properties_data_camera
+
+import properties_data_lamp
+for member in dir(properties_data_lamp):
+ subclass = getattr(properties_data_lamp, member)
+ try:
+ subclass.COMPAT_ENGINES.add('POVRAY_RENDER')
+ except:
+ pass
+del properties_data_lamp
+
+
+
+class RenderButtonsPanel():
+ 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
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine == False) and (rd.engine in cls.COMPAT_ENGINES)
+
+class MaterialButtonsPanel():
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "material"
+ # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here
+
+ @classmethod
+ def poll(cls, context):
+ mat = context.material
+ rd = context.scene.render
+ return mat and (rd.use_game_engine == False) and (rd.engine in cls.COMPAT_ENGINES)
+
+########################################MR######################################
+class MATERIAL_PT_povray_mirrorIOR(MaterialButtonsPanel, bpy.types.Panel):
+ bl_label = "IOR Mirror"
+ COMPAT_ENGINES = {'POVRAY_RENDER'}
+
+ def draw_header(self, context):
+ scene = context.material
+
+ self.layout.prop(scene, "pov_mirror_use_IOR", text="")
+
+ def draw(self, context):
+ layout = self.layout
+
+ mat = context.material
+ layout.active = mat.pov_mirror_use_IOR
+
+ if mat.pov_mirror_use_IOR:
+ split = layout.split()
+ col = split.column()
+ row = col.row()
+ row.alignment = 'CENTER'
+ row.label(text="The current Raytrace ")
+ row = col.row()
+ row.alignment = 'CENTER'
+ row.label(text="Transparency IOR is: "+str(mat.raytrace_transparency.ior))
+
+
+class MATERIAL_PT_povray_metallic(MaterialButtonsPanel, bpy.types.Panel):
+ bl_label = "metallic Mirror"
+ COMPAT_ENGINES = {'POVRAY_RENDER'}
+
+ def draw_header(self, context):
+ scene = context.material
+
+ self.layout.prop(scene, "pov_mirror_metallic", text="")
+
+ def draw(self, context):
+ layout = self.layout
+
+ mat = context.material
+ layout.active = mat.pov_mirror_metallic
+
+class MATERIAL_PT_povray_conserve_energy(MaterialButtonsPanel, bpy.types.Panel):
+ bl_label = "conserve energy"
+ COMPAT_ENGINES = {'POVRAY_RENDER'}
+
+ def draw_header(self, context):
+ mat = context.material
+
+ self.layout.prop(mat, "pov_conserve_energy", text="")
+
+ def draw(self, context):
+ layout = self.layout
+
+ mat = context.material
+ layout.active = mat.pov_conserve_energy
+
+class MATERIAL_PT_povray_iridescence(MaterialButtonsPanel, bpy.types.Panel):
+ bl_label = "iridescence"
+ COMPAT_ENGINES = {'POVRAY_RENDER'}
+
+ def draw_header(self, context):
+ mat = context.material
+
+ self.layout.prop(mat, "pov_irid_enable", text="")
+
+ def draw(self, context):
+ layout = self.layout
+
+ mat = context.material
+ layout.active = mat.pov_irid_enable
+
+ if mat.pov_irid_enable:
+ split = layout.split()
+
+ col = split.column()
+ col.prop(mat, "pov_irid_amount", slider=True)
+ col.prop(mat, "pov_irid_thickness", slider=True)
+ col.prop(mat, "pov_irid_turbulence", slider=True)
+
+
+class MATERIAL_PT_povray_caustics(MaterialButtonsPanel, bpy.types.Panel):
+ bl_label = "Caustics"
+ COMPAT_ENGINES = {'POVRAY_RENDER'}
+
+
+ def draw_header(self, context):
+ mat = context.material
+
+ self.layout.prop(mat, "pov_caustics_enable", text="")
+
+ def draw(self, context):
+
+ layout = self.layout
+
+ mat = context.material
+ layout.active = mat.pov_caustics_enable
+ Radio = 1
+ if mat.pov_caustics_enable:
+ split = layout.split()
+
+ col = split.column()
+ col.prop(mat, "pov_refraction_type")
+## if mat.pov_refraction_type=="0":
+## mat.pov_fake_caustics = False
+## mat.pov_photons_refraction = False
+## mat.pov_photons_reflection = True
+ if mat.pov_refraction_type=="1":
+## mat.pov_fake_caustics = True
+## mat.pov_photons_refraction = False
+ col.prop(mat, "pov_fake_caustics_power", slider=True)
+ elif mat.pov_refraction_type=="2":
+## mat.pov_fake_caustics = False
+## mat.pov_photons_refraction = True
+ col.prop(mat, "pov_photons_dispersion", slider=True)
+ col.prop(mat, "pov_photons_reflection")
+
+
+
+## col.prop(mat, "pov_fake_caustics")
+## if mat.pov_fake_caustics:
+## col.prop(mat, "pov_fake_caustics_power", slider=True)
+## mat.pov_photons_refraction=0
+## else:
+## col.prop(mat, "pov_photons_refraction")
+## if mat.pov_photons_refraction:
+## col.prop(mat, "pov_photons_dispersion", slider=True)
+## Radio = 0
+## mat.pov_fake_caustics=Radio
+## col.prop(mat, "pov_photons_reflection")
+####TODO : MAKE THIS A real RADIO BUTTON (using EnumProperty?)
+######################################EndMR#####################################
+
+class RENDER_PT_povray_radiosity(RenderButtonsPanel, bpy.types.Panel):
+ bl_label = "Radiosity"
+ COMPAT_ENGINES = {'POVRAY_RENDER'}
+
+ def draw_header(self, context):
+ scene = context.scene
+
+ self.layout.prop(scene, "pov_radio_enable", text="")
+
+ def draw(self, context):
+ layout = self.layout
+
+ scene = context.scene
+ rd = scene.render
+
+ layout.active = scene.pov_radio_enable
+
+ split = layout.split()
+
+ col = split.column()
+ col.prop(scene, "pov_radio_count", text="Rays")
+ col.prop(scene, "pov_radio_recursion_limit", text="Recursions")
+ col = split.column()
+ col.prop(scene, "pov_radio_error_bound", text="Error")
+
+ layout.prop(scene, "pov_radio_display_advanced")
+
+ if scene.pov_radio_display_advanced:
+ split = layout.split()
+
+ col = split.column()
+ col.prop(scene, "pov_radio_adc_bailout", slider=True)
+ col.prop(scene, "pov_radio_gray_threshold", slider=True)
+ col.prop(scene, "pov_radio_low_error_factor", slider=True)
+
+ col = split.column()
+ col.prop(scene, "pov_radio_brightness")
+ col.prop(scene, "pov_radio_minimum_reuse", text="Min Reuse")
+ col.prop(scene, "pov_radio_nearest_count")
+
+ split = layout.split()
+
+ col = split.column()
+ col.label(text="Estimation Influence:")
+ col.prop(scene, "pov_radio_media")
+ col.prop(scene, "pov_radio_normal")
+
+ col = split.column()
+ col.prop(scene, "pov_radio_always_sample")
+
+class RENDER_PT_povray_baking(RenderButtonsPanel, bpy.types.Panel):
+ bl_label = "Baking"
+ COMPAT_ENGINES = {'POVRAY_RENDER'}
+
+ def draw_header(self, context):
+ scene = context.scene
+
+ self.layout.prop(scene, "pov_baking_enable", text="")
+
+ def draw(self, context):
+ layout = self.layout
+
+ scene = context.scene
+ rd = scene.render
+
+ layout.active = scene.pov_baking_enable
diff --git a/render_renderfarmfi.py b/render_renderfarmfi.py
new file mode 100644
index 00000000..6e9f318b
--- /dev/null
+++ b/render_renderfarmfi.py
@@ -0,0 +1,966 @@
+# ##### 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": "Renderfarm.fi",
+ "author": "Nathan Letwory <nathan@letworyinteractive.com>, Jesse Kaukonen <jesse.kaukonen@gmail.com>",
+ "version": (5,),
+ "blender": (2, 5, 5),
+ "api": 33713,
+ "location": "Render > Engine > Renderfarm.fi",
+ "description": "Send .blend as session to http://www.renderfarm.fi to render",
+ "warning": "",
+ "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"}
+
+"""
+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
+
+from bpy.props import PointerProperty, StringProperty, BoolProperty, EnumProperty, IntProperty, CollectionProperty
+
+bpy.CURRENT_VERSION = bl_addon_info["version"][0]
+bpy.found_newer_version = False
+bpy.up_to_date = False
+bpy.download_location = 'http://www.renderfarm.fi/blender'
+
+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 renderEngine(render_engine):
+ bpy.types.register(render_engine)
+ return render_engine
+
+
+class ORESession(bpy.types.IDPropertyGroup):
+ pass
+
+class ORESettings(bpy.types.IDPropertyGroup):
+ pass
+
+# entry point for settings collection
+bpy.types.Scene.ore_render = PointerProperty(type=ORESettings, name='ORE Render', description='ORE Render Settings')
+
+# fill the new struct
+ORESettings.username = StringProperty(name='E-mail', description='E-mail for Renderfarm.fi', maxlen=256, default='')
+ORESettings.password = StringProperty(name='Password', description='Renderfarm.fi password', maxlen=256, default='')
+ORESettings.hash = StringProperty(name='Hash', description='hash calculated out of credentials', maxlen=33, default='')
+
+ORESettings.shortdesc = StringProperty(name='Short description', description='A short description of the scene (100 characters)', maxlen=101, default='')
+ORESettings.longdesc = StringProperty(name='Long description', description='A more elaborate description of the scene (2k)', maxlen=2048, default='')
+ORESettings.title = StringProperty(name='Title', description='Title for this session (128 characters)', maxlen=128, default='')
+ORESettings.url = StringProperty(name='Project URL', description='Project URL. Leave empty if not applicable', maxlen=256, default='')
+
+ORESettings.parts = IntProperty(name='Parts/Frame', description='', min=1, max=1000, soft_min=1, soft_max=64, default=1)
+ORESettings.resox = IntProperty(name='Resolution X', description='X of render', min=1, max=10000, soft_min=1, soft_max=10000, default=1920)
+ORESettings.resoy = IntProperty(name='Resolution Y', description='Y of render', min=1, max=10000, soft_min=1, soft_max=10000, default=1080)
+ORESettings.memusage = IntProperty(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.start = IntProperty(name='Start Frame', description='Start Frame', default=1)
+ORESettings.end = IntProperty(name='End Frame', description='End Frame', default=250)
+ORESettings.fps = IntProperty(name='FPS', description='FPS', min=1, max=256, default=25)
+
+ORESettings.prepared = BoolProperty(name='Prepared', description='Set to True if preparation has been run', default=False)
+ORESettings.debug = BoolProperty(name='Debug', description='Verbose output in console', default=False)
+ORESettings.selected_session = IntProperty(name='Selected Session', description='The selected session', default=0)
+ORESettings.hasUnsupportedSimulation = BoolProperty(name='HasSimulation', description='Set to True if therea re unsupported simulations', default=False)
+
+# session struct
+ORESession.name = StringProperty(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.inlicense = EnumProperty(items=licenses, name='source license', description='license speficied for the source files', default='1')
+ORESettings.outlicense = EnumProperty(items=licenses, name='output license', description='license speficied for the output files', default='1')
+
+ORESettings.sessions = CollectionProperty(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():
+ 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
+
+class SUMMARY_PT_RenderfarmFi(RenderButtonsPanel, bpy.types.Panel):
+ # Prints a summary to the panel before uploading. If scene settings differ from ore settings, then display a warning icon
+ bl_label = 'Summary'
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+
+ def draw(self, context):
+ sce = context.scene
+ rd = context.scene.render
+ ore = context.scene.ore_render
+ layout = self.layout
+ problems = False
+
+ # Check if correct resolution is set
+ if rd.resolution_x != ore.resox:
+ layout.label(text='Resolution X: ' + str(ore.resox), icon='ERROR')
+ problems = True
+ else:
+ layout.label(text='Resolution X: ' + str(ore.resox), icon='FILE_TICK')
+ if rd.resolution_y != ore.resoy:
+ layout.label(text='Resolution Y: ' + str(ore.resoy), icon='ERROR')
+ problems = True
+ else:
+ layout.label(text='Resolution Y: ' + str(ore.resoy), icon='FILE_TICK')
+
+ # Check if correct number of frames is specified
+ if (sce.frame_start != ore.start) and not ( (sce.frame_start == ore.start and sce.frame_end == ore.end) and sce.frame_start == sce.frame_end):
+ layout.label(text='Start frame: ' + str(ore.start), icon='ERROR')
+ layout.label(text='.blend Start frame is different to the one specified in the uploader script. Please verify!')
+ problems = True
+ else:
+ layout.label(text='Start frame: ' + str(ore.start), icon='FILE_TICK')
+ if (sce.frame_end != ore.end) and not ( (sce.frame_start == ore.start and sce.frame_end == ore.end) and sce.frame_start == sce.frame_end):
+ layout.label(text='End frame: ' + str(ore.end), icon='ERROR')
+ layout.label(text='.blend End frame is different to the one specified in the uploader script. Please verify!')
+ problems = True
+ else:
+ layout.label(text='End frame: ' + str(ore.end), icon='FILE_TICK')
+
+ # Check if more than 1 frame is specified
+ if (sce.frame_start == ore.start and sce.frame_end == ore.end) and (sce.frame_start == sce.frame_end):
+ layout.label(text='Only one frame specified to be rendered!')
+ layout.label(text='This is highly ineffective when using distributed rendering')
+ problems = True
+
+ if rd.resolution_percentage != 100:
+ layout.label(text='Resolution percentage: ' + str(rd.resolution_percentage), icon='ERROR')
+ problems = True
+ else:
+ layout.label(text='Resolution percentage: ' + str(rd.resolution_percentage), icon='FILE_TICK')
+
+ if rd.file_format != 'PNG':
+ layout.label(text='Output format: ' + rd.file_format, icon='ERROR')
+ layout.label(text='Output format must be set to PNG')
+ problems = True
+ else:
+ layout.label(text='Output format: ' + rd.file_format, icon='FILE_TICK')
+
+ if ore.parts > 1 and rd.use_sss == True:
+ layout.label(text='Subsurface Scattering: ' + str(rd.use_sss), icon='ERROR')
+ layout.label(text='If you want to use SSS, parts must be set to 1')
+ problems = True
+ else:
+ layout.label(text='Subsurface Scattering: ' + str(rd.use_sss), icon='FILE_TICK')
+
+ if rd.use_compositing == False:
+ layout.label(text='Composite nodes: ' + str(rd.use_compositing), icon='ERROR')
+ layout.label(text='Composite nodes are disabled.')
+ layout.label(text='The script automatically disables them if: ')
+ layout.label(text='- Filter type nodes are used and parts are more than 1')
+ layout.label(text='- There is an output node')
+ problems = True
+ else:
+ layout.label(text='Composite nodes: ' + str(rd.use_compositing), icon='FILE_TICK')
+
+ if rd.use_save_buffers:
+ layout.label(text='Save buffers: ' + str(rd.use_save_buffers), icon='ERROR')
+ layout.label(text='Save buffers must be disabled')
+ layout.label(text='Can only disabled if Full Sample is turned off')
+ problems = True
+ else:
+ layout.label(text='Save buffers: ' + str(rd.use_save_buffers), icon='FILE_TICK')
+
+ if rd.threads_mode != 'FIXED' or rd.threads > 1:
+ layout.label(text='Threads: ' + rd.threads_mode + ' ' + str(rd.threads), icon='ERROR')
+ layout.label(text='Threads must be set to Fixed, 1')
+ problems = True
+ else:
+ layout.label(text='Threads: ' + rd.threads_mode + ' ' + str(rd.threads), icon='FILE_TICK')
+
+ if ore.hasUnsupportedSimulation == True:
+ layout.label(text='There is an unsupported simulation', icon='ERROR')
+ layout.label(text='Fluid/smoke/cloth/collision/softbody simulations are not supported')
+ problems = True
+ else:
+ layout.label(text='No unsupported simulations found', icon='FILE_TICK')
+
+ if problems == False and ore.prepared == False:
+ bpy.ops.ore.prepare()
+
+ if ore.prepared == False:
+ layout.label(text='The script reports "not ready".', icon='ERROR')
+ layout.label(text='Please review the settings above')
+ layout.label(text='If everything is in order, click Check Scene again')
+ layout.label(text='The script automatically changes settings, so make sure they are correct')
+ else:
+ layout.label(text='The script reports "All settings ok!"', icon='FILE_TICK')
+ layout.label(text='Please render one frame using Blender Render first')
+ row = layout.row()
+ row.operator('ore.testRender')
+ layout.label(text='If you are sure that the render works, click Render on Renderfarm.fi')
+
+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, bpy.types.Panel):
+ bl_label = 'Login to Renderfarm.fi'
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+
+ 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, bpy.types.Panel):
+ bl_label = 'Check for updates'
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+
+ 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, bpy.types.Panel):
+ bl_label = 'Sessions'
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+
+ 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, bpy.types.Panel):
+ bl_label = "Scene Settings"
+ COMPAT_ENGINES = set(['RENDERFARMFI_RENDER'])
+
+ @classmethod
+ def poll(cls, context):
+ rd = context.scene.render
+ return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES)
+
+ 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()
+
+ 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
+ if totFrames != 0:
+ done = math.floor((self.frames / totFrames)*100)
+ else:
+ done = math.floor((self.frames / (totFrames+0.01))*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.use:
+ 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()
+
+ def isFilterNode(node):
+ t = type(node)
+ return t==bpy.types.CompositorNodeBlur or t==bpy.types.CompositorNodeDBlur
+
+ def hasCompositingErrors(use_nodes, nodetree, parts):
+ if not use_nodes: # no nodes in use, ignore check
+ return False
+
+ for node in nodetree.nodes:
+ # output file absolutely forbidden
+ if type(node)==bpy.types.CompositorNodeOutputFile:
+ self.report({'ERROR'}, 'File output node is disallowed, remove them from your compositing nodetrees.')
+ return True
+ # blur et al are problematic when rendering ore.parts>1
+ if isFilterNode(node) and parts>1:
+ self.report({'WARNING'}, 'A filtering node found and parts > 1. This combination will give bad output.')
+ return True
+
+ return False
+
+ sce = context.scene
+ ore = sce.ore_render
+
+ errors = False
+
+ checkStatus(ore)
+
+ if len(bpy.errors) > 0:
+ ore.prepared = False
+ return {'CANCELLED'}
+
+ rd = sce.render
+
+ bpy.ops.file.pack_all()
+ 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.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")
+ rd.use_sss = False # disabling because ore.parts > 1. It's ok to use SSS with 1part/frame
+ 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")
+ ore.hasUnsupportedSimulation = True
+ errors = True
+ else:
+ ore.hasUnsupportedSimulation = False
+ rd.use_save_buffers = False
+ rd.use_free_image_textures = True
+ if rd.use_compositing:
+ if hasCompositingErrors(sce.use_nodes, sce.node_tree, ore.parts):
+ print("Found disallowed nodes or problematic setup")
+ rd.use_compositing = False
+ self.report({'WARNING'}, "Found disallowed nodes or problematic setup")
+ errors = 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 found, don't allow to upload, instead have user
+ # go through this until everything is ok
+ if errors:
+ self.report({'WARNING'}, "Settings were changed or other issues found. Check console and do a test render to make sure everything works.")
+ ore.prepared = False
+ else:
+ 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
+ sce.render.threads_mode = 'AUTO'
+ return {'FINISHED'}
+
+class ORE_TestRenderOp(bpy.types.Operator):
+ bl_idname = "ore.testRender"
+ bl_label = "Run a test render"
+
+ def execute(self, context):
+ rd = context.scene.render
+ rd.engine = 'BLENDER_RENDER'
+ rd.threads_mode = 'AUTO'
+ rd.threads = 1
+ bpy.ops.render.render()
+ rd.threads_mode = 'FIXED'
+ rd.threads = 1
+ rd.engine = 'RENDERFARMFI_RENDER'
+ 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 settings"
+
+ def execute(self, context):
+ sce = context.scene
+ ore = sce.ore_render
+ rd = context.scene.render
+
+ ore.resox = rd.resolution_x
+ ore.resoy = rd.resolution_y
+ ore.start = sce.frame_start
+ ore.end = sce.frame_end
+ ore.fps = rd.fps
+
+ 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.INFO_MT_render.append(menu_export)
+
+def unregister():
+ bpy.types.INFO_MT_render.remove(menu_export)
+
+if __name__ == "__main__":
+ register()
diff --git a/rigify/README b/rigify/README
new file mode 100644
index 00000000..39e8cda6
--- /dev/null
+++ b/rigify/README
@@ -0,0 +1,252 @@
+INTRODUCTION
+------------
+Rigify is an auto-rigging system based on a "building blocks" paradigm. The
+user can create a rig by putting together any combination of rig types, in any
+confguration that they want.
+
+A rig type is something like "biped arm" or "spine" or "finger".
+
+The input to the Rigify system is something called a "metarig". It is an
+armature that contains data about how to construct the rig. In particular, it
+contains bones in the basic configuration of the rig, with some bones tagged
+to indicate the rig type.
+
+For example, a metaig might contain a chain of three bones, the root-most of
+which is tagged as being a biped arm. When given as input to Rigify, Rigify
+will then generate a fully-featured biped arm rig in the same position and
+proportions as the 3-bone chain.
+
+One could also have another chain of bones, the root-most of which is tagged as
+being a spine. And the root-most bone of the arm chain could be the child of
+any of those spine bones. Then the rig that Rigify would generate would be a
+spine rig with an arm rig attached to it.
+
+
+THE GUTS OF RIGIFY, SUMMARIZED
+------------------------------
+The concept behind rigify is fairly simple. It recieves an armature as input
+with some of the bones tagged as being certain rig types (arm, leg, etc.)
+
+When Rigify recieves that armature as input, the first thing it does is
+duplicate the armature. From here on out, the original armature is totally
+ignored. Only the duplicate is used. And this duplicate armature object will
+become the generated rig.
+
+Rigify next prepends "ORG-" to all of the bones. These are the "original"
+bones of the metarig, and they are used as the glue between rig types, as I
+will explain later.
+
+Rigify then generates the rig in two passes. The first pass is the
+"information gathering" stage.
+
+The information gathering stage doesn't modify the armature at all. It simply
+gathers information about it. Or, rather, it lets the rig types gather
+information about it.
+It traverses the bones in a root-most to leaf-most order, and whenever it
+stumbles upon a bone that has a rig type tagged on it, it creates a rig-type
+python object (rig types will be explained further down) for that rig type,
+and executes the resulting object's information gathering code.
+
+At the end of the information gathering stage, Rigify has a collection of
+python objects, each of which know all the information they need to generate
+their own bit of the rig.
+
+The next stage is the rig generation stage. This part is pretty simple. All
+Rigify does is it loops over all of the rig-type python objects that it created
+in the previous stage (also in root-most to leaf-most order), and executes
+their rig-generate code. All of the actual rig generation happens in the
+rig-type python objects.
+
+And that's pretty much it. As you can see, most of the important code is
+actually in the rig types themselves, not in Rigify. Rigify is pretty sparse
+when it comes right down to it.
+
+There is one final stage to rig generation. Rigify checks all of the bones
+for "DEF-", "MCH-", and "ORG-" prefixes, and moves those bones to their own
+layers. It also sets all of the "DEF-" bones to deform, and sets all other
+bones to _not_ deform. And finally, it looks for any bone that does not have
+a parent, and sets the root bone (which Rigify creates) as their parent.
+
+
+THE GUTS OF A RIG TYPE, BASIC
+-----------------------------
+A rig type is simply a python module containing a class named "Rig". The Rig
+class is only required to have two methods: __init__() and generate()
+
+__init__() is the "information gathering" code for the rig type. When Rigify
+loops through the bones and finds a tagged bone, it will create a python
+object from the Rig class, executing this method.
+In addition to the default "self" parameter, __init__() needs to take the
+armature object, the name of the bone that was tagged, and the bone's rig type
+parameters object.
+
+A proper rig-type __init__() will look like this:
+
+ def __init__(self, obj, bone, params):
+ # code goes here
+
+At the bare minimum, you are going to want to store the object and bone name
+in the rig type object for later reference in the generate method. So:
+
+ def __init__(self, obj, bone, params):
+ self.obj = obj
+ self.org_bone = bone
+
+Most rig types involve more than just that one bone, though, so you will also
+want to store the names of any other relevant bones. For example, maybe the
+parent of the tagged bone:
+
+ def __init__(self, obj, bone, params):
+ self.obj = obj
+ self.org_bone = bone
+ self.org_parent = obj.data.bones[bone].parent.name
+
+It is important that store the _names_ of the bones, and not direct references.
+Due to how the armature system in Blender works, when flipping in and out of
+edit mode, pose-bone and edit-bone references get lost. (Arg...)
+
+Remember that it is critical that the information-gathering method does _not_
+modify the armature in any way. This way all of the rig type's info-gathering
+methods can execute on a clean armature. Many rig types depend on traversing
+parent-child relationships to figure out what bones are relevant to them, for
+example.
+
+
+Next is the generate() method. This is the method that Rigify calls to
+actually generate the rig. It takes the form:
+
+ def generate(self):
+ # code goes here
+
+It doesn't take any parameters beyond "self". So you really need to store any
+information you need with the __init__() method.
+
+Generate pretty much has free reign to do whatever it wants, with the exception
+of two simple rules:
+1. Other than the "ORG-" bones, do not touch anything that is not created by
+this method (this prevents rig types from messing each other up).
+2. Even with "ORG-" bones, the only thing you are allowed to do is add children
+and add constraints. Do not rename them, do not remove children or
+constraints, and especially do not change their parents. (Adding constraints
+and adding children are encouraged, though. ;-)) This is because the "ORG-"
+bones are the glue that holds everything together, and changing them beyond
+adding children/constraints ruins the glue, so to speak.
+
+In short: with the exception of adding children/constraints to "ORG-"
+bones, only mess with things that you yourself create.
+
+It is also generally a good idea (though not strictly required) that the rig
+type add constraints to the "ORG-" bones it was generated from so that the
+"ORG-" bones move with the animation controls.
+For example, if I make a simple arm rig type, the controls that the animator
+uses should also move the "ORG-" bones. That way, any other rig-types that are
+children of those "ORG-" bones will move along with them. For example, any
+fingers on the end of the arm.
+
+Also, any bones that the animator should not directly animate with should have
+their names prefixed with "DEF-" or "MCH-". The former if it is a bone that
+is intended to deform the mesh, the latter if it is not.
+It should be obvious, then, that a bone cannot be both an animation control and
+a deforming bone in Rigify. This is on purpose.
+
+Also note that there are convenience functions in utils.py for prepending
+"DEF-" and "MCH-" to bone names: deformer() and mch()
+There is also a convenience function for stripping "ORG-" from a bone name:
+strip_org()
+Which is useful for removing "ORG-" from bones you create by duplicating
+the "ORG-" bones.
+I recommend you use these functions instead of manually adding/stripping
+these prefixes. That way if the prefixes are changed, it can be changed in
+one place (those functions) and all the rig types will still work.
+
+
+THE GUTS OF A RIG TYPE, ADVANCED
+--------------------------------
+If you look at any of the rig types included with Rigify, you'll note that they
+have several more methods than just __init__() and generate().
+THESE ADDITIONAL METHODS ARE _NOT_ REQUIRED for a rig type to function. But
+they can add some nifty functionality to your rig.
+
+Not all of the additional methods you see in the included rig types have any
+special purpose for Rigify, however. For example, I often create separate
+methods for generating the deformation and control rigs, and then call them
+both from the main generate() method. But that is just for organization, and
+has nothing to do with Rigify itself.
+
+Here are the additional methods relevant to Rigify, with brief decriptions of
+what they are for:
+
+
+RIG PARAMETERS
+--------------
+For many rig types, it is handy for the user to be able to tweak how they are
+generated. For example, the included biped arm rig allows the user to specify
+the axis of rotation for the elbow.
+
+There are two methods necessary to give a rig type user-tweakable parameters,
+both of which must be class methods:
+add_parameters()
+parameters_ui()
+
+add_parameters() takes an IDPropertyGroup as input, and adds its parameters
+to that group as RNA properties. For example:
+
+ @classmethod
+ def add_parameters(self, group):
+ group.toggle_param = bpy.props.BoolProperty(name="Test toggle:", default=False, description="Just a test, not really used for anything.")
+
+parameter_ui() recieves a Blender UILayout object, the metarig object, and the
+tagged bone name. It creates a GUI in the UILayout for the user to tweak the
+parameters. For example:
+
+ @classmethod
+ def parameters_ui(self, layout, obj, bone):
+ params = obj.pose.bones[bone].rigify_parameters[0]
+ r = layout.row()
+ r.prop(params, "toggle_param")
+
+
+SAMPLE METARIG
+--------------
+It is a good idea for all rig types to have a sample metarig that the user can
+add to their own metarig. This is what the create_sample() method is for.
+Like the parameter methods above, create_sample() must be a class method.
+
+create_sample() takes the current armature object as input, and adds the bones
+for its rig-type's metarig. For example:
+
+ @classmethod
+ def create_sample(self, obj):
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bone = arm.edit_bones.new('Bone')
+ bone.head[:] = 0.0000, 0.0000, 0.0000
+ bone.tail[:] = 0.0000, 0.0000, 1.0000
+ bone.roll = 0.0000
+ bone.use_connect = False
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bone]
+ pbone.rigify_type = 'copy'
+ pbone.rigify_parameters.add()
+
+Obviously, this isn't something that you generally want to hand-code,
+especially with more complex samples. There is a function in utils.py
+that will generate the code for create_sample() for you, based on a selected
+armature. The function is called write_metarig()
+
+
+GENERATING A PYTHON UI
+----------------------
+The generate() method can also, optionally, return python code as a string.
+This python code is added to the "rig properties" panel that gets
+auto-generated along with the rig. This is useful for exposing things like
+IK/FK switches in a nice way to the animator.
+
+The string must be returned in a list:
+
+return ["my python code"]
+
+Otherwise it won't work.
+
diff --git a/rigify/__init__.py b/rigify/__init__.py
new file mode 100644
index 00000000..4a431e3a
--- /dev/null
+++ b/rigify/__init__.py
@@ -0,0 +1,146 @@
+#====================== 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": "Rigify",
+ "author": "Nathan Vegdahl",
+ "version": (0, 5),
+ "blender": (2, 5, 5),
+ "api": 33110,
+ "location": "Armature properties",
+ "wiki_url": "",
+ "tracker_url": "",
+ "category": "Rigging"}
+
+if "bpy" in locals():
+ import imp
+ imp.reload(generate)
+ imp.reload(ui)
+ imp.reload(utils)
+ imp.reload(metarig_menu)
+else:
+ from . import generate, ui, utils, metarig_menu
+
+import bpy
+import bpy_types
+import os
+
+
+def get_rig_list(path):
+ """ Recursively searches for rig types, and returns a list.
+ """
+ rigs = []
+ MODULE_DIR = os.path.dirname(__file__)
+ RIG_DIR_ABS = os.path.join(MODULE_DIR, utils.RIG_DIR)
+ SEARCH_DIR_ABS = os.path.join(RIG_DIR_ABS, path)
+ files = os.listdir(SEARCH_DIR_ABS)
+ files.sort()
+
+ for f in files:
+ is_dir = os.path.isdir(os.path.join(SEARCH_DIR_ABS, f)) # Whether the file is a directory
+ if f[0] in (".", "_"):
+ pass
+ elif f.count(".") >= 2 or (is_dir and "." in f):
+ print("Warning: %r, filename contains a '.', skipping" % os.path.join(SEARCH_DIR_ABS, f))
+ else:
+ if is_dir:
+ # Check directories
+ module_name = os.path.join(path, f).replace(os.sep, ".")
+ try:
+ rig = utils.get_rig_type(module_name)
+ except ImportError as e:
+ print("Rigify: " + str(e))
+ else:
+ # Check if it's a rig itself
+ if not hasattr(rig, "Rig"):
+ # Check for sub-rigs
+ ls = get_rig_list(os.path.join(path, f, "")) # "" adds a final slash
+ rigs.extend(["%s.%s" % (f, l) for l in ls])
+ else:
+ rigs += [f]
+
+ elif f.endswith(".py"):
+ # Check straight-up python files
+ t = f[:-3]
+ module_name = os.path.join(path, t).replace(os.sep, ".")
+ try:
+ utils.get_rig_type(module_name).Rig
+ except (ImportError, AttributeError):
+ pass
+ else:
+ rigs += [t]
+ rigs.sort()
+ return rigs
+
+
+rig_list = get_rig_list("")
+
+
+collection_list = []
+for r in rig_list:
+ a = r.split(".")
+ if len(a) >= 2 and a[0] not in collection_list:
+ collection_list += [a[0]]
+
+
+col_enum_list = [("All", "All", ""), ("None", "None", "")]
+for c in collection_list:
+ col_enum_list += [(c, c, "")]
+
+
+class RigifyName(bpy.types.IDPropertyGroup):
+ name = bpy.props.StringProperty()
+
+
+class RigifyParameters(bpy.types.IDPropertyGroup):
+ name = bpy.props.StringProperty()
+
+
+for rig in rig_list:
+ r = utils.get_rig_type(rig).Rig
+ try:
+ r.add_parameters(RigifyParameters)
+ except AttributeError:
+ pass
+
+
+##### REGISTER #####
+
+def register():
+ bpy.types.PoseBone.rigify_type = bpy.props.StringProperty(name="Rigify Type", description="Rig type for this bone.")
+ bpy.types.PoseBone.rigify_parameters = bpy.props.CollectionProperty(type=RigifyParameters)
+
+ IDStore = bpy.types.WindowManager
+ IDStore.rigify_collection = bpy.props.EnumProperty(items=col_enum_list, default="All", name="Rigify Active Collection", description="The selected rig collection")
+ IDStore.rigify_types = bpy.props.CollectionProperty(type=RigifyName)
+ IDStore.rigify_active_type = bpy.props.IntProperty(name="Rigify Active Type", description="The selected rig type.")
+
+ metarig_menu.register()
+
+
+def unregister():
+ del bpy.types.PoseBone.rigify_type
+ del bpy.types.PoseBone.rigify_parameters
+
+ IDStore = bpy.types.WindowManager
+ del IDStore.rigify_collection
+ del IDStore.rigify_types
+ del IDStore.rigify_active_type
+
+ metarig_menu.unregister()
+
diff --git a/rigify/generate.py b/rigify/generate.py
new file mode 100644
index 00000000..1134b6d6
--- /dev/null
+++ b/rigify/generate.py
@@ -0,0 +1,351 @@
+#====================== 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
+import time
+import traceback
+import sys
+from rna_prop_ui import rna_idprop_ui_prop_get
+from rigify.utils import MetarigError, new_bone, get_rig_type
+from rigify.utils import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name
+from rigify.utils import RIG_DIR
+from rigify.utils import create_root_widget
+from rigify.utils import random_string
+from rigify.rig_ui_template import UI_SLIDERS, layers_ui
+from rigify import rigs
+
+RIG_MODULE = "rigs"
+ORG_LAYER = [n == 31 for n in range(0, 32)] # Armature layer that original bones should be moved to.
+MCH_LAYER = [n == 30 for n in range(0, 32)] # Armature layer that mechanism bones should be moved to.
+DEF_LAYER = [n == 29 for n in range(0, 32)] # Armature layer that deformation bones should be moved to.
+ROOT_LAYER = [n == 28 for n in range(0, 32)] # Armature layer that root bone should be moved to.
+
+
+class Timer:
+ def __init__(self):
+ self.timez = time.time()
+
+ def tick(self, string):
+ t = time.time()
+ print(string + "%.3f" % (t - self.timez))
+ self.timez = t
+
+
+# TODO: generalize to take a group as input instead of an armature.
+def generate_rig(context, metarig):
+ """ Generates a rig from a metarig.
+
+ """
+ t = Timer()
+ rig_id = random_string(12) # Random so that different rigs don't collide id's
+
+ # Initial configuration
+ use_global_undo = context.user_preferences.edit.use_global_undo
+ context.user_preferences.edit.use_global_undo = False
+ mode_orig = context.mode
+ rest_backup = metarig.data.pose_position
+ metarig.data.pose_position = 'REST'
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ scene = context.scene
+
+ #------------------------------------------
+ # Create/find the rig object and set it up
+
+ # Check if the generated rig already exists, so we can
+ # regenerate in the same object. If not, create a new
+ # object to generate the rig in.
+ print("Fetch rig.")
+ try:
+ name = metarig["rig_object_name"]
+ except KeyError:
+ name = "rig"
+
+ try:
+ obj = scene.objects[name]
+ except KeyError:
+ obj = bpy.data.objects.new(name, bpy.data.armatures.new(name))
+ obj.draw_type = 'WIRE'
+ scene.objects.link(obj)
+
+ obj.data.pose_position = 'POSE'
+
+ # Get rid of anim data in case the rig already existed
+ print("Clear rig animation data.")
+ obj.animation_data_clear()
+
+ # Select generated rig object
+ metarig.select = False
+ obj.select = True
+ scene.objects.active = obj
+
+ # Remove all bones from the generated rig armature.
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in obj.data.edit_bones:
+ obj.data.edit_bones.remove(bone)
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Create temporary duplicates for merging
+ temp_rig_1 = metarig.copy()
+ temp_rig_1.data = metarig.data.copy()
+ scene.objects.link(temp_rig_1)
+
+ temp_rig_2 = metarig.copy()
+ temp_rig_2.data = obj.data
+ scene.objects.link(temp_rig_2)
+
+ # Select the temp rigs for merging
+ for objt in scene.objects:
+ objt.select = False # deselect all objects
+ temp_rig_1.select = True
+ temp_rig_2.select = True
+ scene.objects.active = temp_rig_2
+
+ # Merge the temporary rigs
+ bpy.ops.object.join()
+
+ # Delete the second temp rig
+ bpy.ops.object.delete()
+
+ # Select the generated rig
+ for objt in scene.objects:
+ objt.select = False # deselect all objects
+ obj.select = True
+ scene.objects.active = obj
+
+ # Copy over the pose_bone properties
+ for bone in metarig.pose.bones:
+ bone_gen = obj.pose.bones[bone.name]
+
+ # Rotation mode and transform locks
+ bone_gen.rotation_mode = bone.rotation_mode
+ bone_gen.lock_rotation = tuple(bone.lock_rotation)
+ bone_gen.lock_rotation_w = bone.lock_rotation_w
+ bone_gen.lock_rotations_4d = bone.lock_rotations_4d
+ bone_gen.lock_location = tuple(bone.lock_location)
+ bone_gen.lock_scale = tuple(bone.lock_scale)
+
+ # Custom properties
+ for prop in bone.keys():
+ bone_gen[prop] = bone[prop]
+
+ # Copy over bone properties
+ for bone in metarig.data.bones:
+ bone_gen = obj.data.bones[bone.name]
+
+ # B-bone stuff
+ bone_gen.bbone_segments = bone.bbone_segments
+ bone_gen.bbone_in = bone.bbone_in
+ bone_gen.bbone_out = bone.bbone_out
+
+ t.tick("Duplicate rig: ")
+ #----------------------------------
+ # Make a list of the original bones so we can keep track of them.
+ original_bones = [bone.name for bone in obj.data.bones]
+
+ # Add the ORG_PREFIX to the original bones.
+ bpy.ops.object.mode_set(mode='OBJECT')
+ for i in range(0, len(original_bones)):
+ obj.data.bones[original_bones[i]].name = make_original_name(original_bones[i])
+ original_bones[i] = make_original_name(original_bones[i])
+
+ # Create a sorted list of the original bones, sorted in the order we're
+ # going to traverse them for rigging.
+ # (root-most -> leaf-most, alphabetical)
+ bones_sorted = []
+ for name in original_bones:
+ bones_sorted += [name]
+ bones_sorted.sort() # first sort by names
+ bones_sorted.sort(key=lambda bone: len(obj.pose.bones[bone].parent_recursive)) # then parents before children
+
+ t.tick("Make list of org bones: ")
+ #----------------------------------
+ # Create the root bone.
+ bpy.ops.object.mode_set(mode='EDIT')
+ root_bone = new_bone(obj, ROOT_NAME)
+ obj.data.edit_bones[root_bone].head = (0, 0, 0)
+ obj.data.edit_bones[root_bone].tail = (0, 1, 0)
+ obj.data.edit_bones[root_bone].roll = 0
+ bpy.ops.object.mode_set(mode='OBJECT')
+ obj.data.bones[root_bone].layers = ROOT_LAYER
+ # Put the rig_name in the armature custom properties
+ rna_idprop_ui_prop_get(obj.data, "rig_id", create=True)
+ obj.data["rig_id"] = rig_id
+
+ t.tick("Create root bone: ")
+ #----------------------------------
+ try:
+ # Collect/initialize all the rigs.
+ rigs = []
+ deformation_rigs = []
+ for bone in bones_sorted:
+ bpy.ops.object.mode_set(mode='EDIT')
+ rigs += get_bone_rigs(obj, bone)
+ t.tick("Initialize rigs: ")
+
+ # Generate all the rigs.
+ ui_scripts = []
+ for rig in rigs:
+ # Go into editmode in the rig armature
+ bpy.ops.object.mode_set(mode='OBJECT')
+ context.scene.objects.active = obj
+ obj.select = True
+ bpy.ops.object.mode_set(mode='EDIT')
+ scripts = rig.generate()
+ if scripts != None:
+ ui_scripts += [scripts[0]]
+ t.tick("Generate rigs: ")
+ except Exception as e:
+ # Cleanup if something goes wrong
+ print("Rigify: failed to generate rig.")
+ context.user_preferences.edit.use_global_undo = use_global_undo
+ metarig.data.pose_position = rest_backup
+ obj.data.pose_position = 'POSE'
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Continue the exception
+ raise e
+
+ #----------------------------------
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Get a list of all the bones in the armature
+ bones = [bone.name for bone in obj.data.bones]
+
+ # Parent any free-floating bones to the root.
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in bones:
+ if obj.data.edit_bones[bone].parent == None:
+ obj.data.edit_bones[bone].use_connect = False
+ obj.data.edit_bones[bone].parent = obj.data.edit_bones[root_bone]
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Every bone that has a name starting with "DEF-" make deforming. All the
+ # others make non-deforming.
+ for bone in bones:
+ if obj.data.bones[bone].name.startswith(DEF_PREFIX):
+ obj.data.bones[bone].use_deform = True
+ else:
+ obj.data.bones[bone].use_deform = False
+
+ # Move all the original bones to their layer.
+ for bone in original_bones:
+ obj.data.bones[bone].layers = ORG_LAYER
+
+ # Move all the bones with names starting with "MCH-" to their layer.
+ for bone in bones:
+ if obj.data.bones[bone].name.startswith(MCH_PREFIX):
+ obj.data.bones[bone].layers = MCH_LAYER
+
+ # Move all the bones with names starting with "DEF-" to their layer.
+ for bone in bones:
+ if obj.data.bones[bone].name.startswith(DEF_PREFIX):
+ obj.data.bones[bone].layers = DEF_LAYER
+
+ # Create root bone widget
+ create_root_widget(obj, "root")
+
+ # Assign shapes to bones
+ # Object's with name WGT-<bone_name> get used as that bone's shape.
+ for bone in bones:
+ wgt_name = (WGT_PREFIX + obj.data.bones[bone].name)[:21] # Object names are limited to 21 characters... arg
+ if wgt_name in context.scene.objects:
+ # Weird temp thing because it won't let me index by object name
+ for ob in context.scene.objects:
+ if ob.name == wgt_name:
+ obj.pose.bones[bone].custom_shape = ob
+ break
+ # This is what it should do:
+ # obj.pose.bones[bone].custom_shape = context.scene.objects[wgt_name]
+ # Reveal all the layers with control bones on them
+ vis_layers = [False for n in range(0, 32)]
+ for bone in bones:
+ for i in range(0, 32):
+ vis_layers[i] = vis_layers[i] or obj.data.bones[bone].layers[i]
+ for i in range(0, 32):
+ vis_layers[i] = vis_layers[i] and not (ORG_LAYER[i] or MCH_LAYER[i] or DEF_LAYER[i])
+ obj.data.layers = vis_layers
+
+ # Generate the UI script
+ if "rig_ui.py" in bpy.data.texts:
+ script = bpy.data.texts["rig_ui.py"]
+ script.clear()
+ else:
+ script = bpy.data.texts.new("rig_ui.py")
+ script.write(UI_SLIDERS % rig_id)
+ for s in ui_scripts:
+ script.write("\n " + s.replace("\n", "\n ") + "\n")
+ script.write(layers_ui(vis_layers))
+ script.use_module = True
+
+ t.tick("The rest: ")
+ #----------------------------------
+ # Deconfigure
+ bpy.ops.object.mode_set(mode='OBJECT')
+ metarig.data.pose_position = rest_backup
+ obj.data.pose_position = 'POSE'
+ context.user_preferences.edit.use_global_undo = use_global_undo
+
+
+def get_bone_rigs(obj, bone_name, halt_on_missing=False):
+ """ Fetch all the rigs specified on a bone.
+ """
+ rigs = []
+ rig_type = obj.pose.bones[bone_name].rigify_type
+ rig_type = rig_type.replace(" ", "")
+
+ if rig_type == "":
+ pass
+ else:
+ # Gather parameters
+ try:
+ params = obj.pose.bones[bone_name].rigify_parameters[0]
+ except (KeyError, IndexError):
+ params = None
+
+ # Get the rig
+ try:
+ rig = get_rig_type(rig_type).Rig(obj, bone_name, params)
+ except ImportError:
+ message = "Rig Type Missing: python module for type '%s' not found (bone: %s)" % (t, bone_name)
+ if halt_on_missing:
+ raise MetarigError(message)
+ else:
+ print(message)
+ print('print_exc():')
+ traceback.print_exc(file=sys.stdout)
+ else:
+ rigs += [rig]
+ return rigs
+
+
+def param_matches_type(param_name, rig_type):
+ """ Returns True if the parameter name is consistent with the rig type.
+ """
+ if param_name.rsplit(".", 1)[0] == rig_type:
+ return True
+ else:
+ return False
+
+
+def param_name(param_name, rig_type):
+ """ Get the actual parameter name, sans-rig-type.
+ """
+ return param_name[len(rig_type) + 1:]
+
diff --git a/rigify/metarig_menu.py b/rigify/metarig_menu.py
new file mode 100644
index 00000000..65dbbce9
--- /dev/null
+++ b/rigify/metarig_menu.py
@@ -0,0 +1,56 @@
+# ##### 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
+import mathutils
+from rigify.metarigs import human
+from math import cos, sin, pi
+
+
+class AddHuman(bpy.types.Operator):
+ '''Add an advanced human metarig base'''
+ bl_idname = "object.armature_human_advanced_add"
+ bl_label = "Add Humanoid (advanced metarig)"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ bpy.ops.object.armature_add()
+ obj = context.active_object
+ mode_orig = obj.mode
+ bpy.ops.object.mode_set(mode='EDIT') # grr, remove bone
+ bones = context.active_object.data.edit_bones
+ bones.remove(bones[0])
+ human.create(obj)
+ bpy.ops.object.mode_set(mode=mode_orig)
+ return {'FINISHED'}
+
+
+# Add to a menu
+menu_func = (lambda self, context: self.layout.operator(AddHuman.bl_idname,
+ icon='OUTLINER_OB_ARMATURE', text="Human (Meta-Rig)"))
+
+
+def register():
+ #bpy.types.register(AddHuman)
+ bpy.types.INFO_MT_armature_add.append(menu_func)
+
+
+def unregister():
+ #bpy.types.unregister(AddHuman)
+ bpy.types.INFO_MT_armature_add.remove(menu_func)
+
diff --git a/rigify/metarigs/__init__.py b/rigify/metarigs/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/rigify/metarigs/__init__.py
diff --git a/rigify/metarigs/human.py b/rigify/metarigs/human.py
new file mode 100644
index 00000000..08f8b230
--- /dev/null
+++ b/rigify/metarigs/human.py
@@ -0,0 +1,1087 @@
+# ##### 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
+
+
+def create(obj):
+ # generated by rigify.utils.write_metarig
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bones = {}
+
+ bone = arm.edit_bones.new('hips')
+ bone.head[:] = 0.0000, 0.0552, 1.0099
+ bone.tail[:] = 0.0000, 0.0172, 1.1837
+ bone.roll = -3.1416
+ bone.use_connect = False
+ bones['hips'] = bone.name
+ bone = arm.edit_bones.new('spine')
+ bone.head[:] = 0.0000, 0.0172, 1.1837
+ bone.tail[:] = 0.0000, 0.0004, 1.3418
+ bone.roll = -3.1416
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['hips']]
+ bones['spine'] = bone.name
+ bone = arm.edit_bones.new('thigh.L')
+ bone.head[:] = 0.0980, 0.0124, 1.0720
+ bone.tail[:] = 0.0980, -0.0286, 0.5372
+ bone.roll = 0.0000
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hips']]
+ bones['thigh.L'] = bone.name
+ bone = arm.edit_bones.new('thigh.R')
+ bone.head[:] = -0.0980, 0.0124, 1.0720
+ bone.tail[:] = -0.0980, -0.0286, 0.5372
+ bone.roll = 0.0000
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hips']]
+ bones['thigh.R'] = bone.name
+ bone = arm.edit_bones.new('ribs')
+ bone.head[:] = 0.0000, 0.0004, 1.3418
+ bone.tail[:] = 0.0000, 0.0114, 1.6582
+ bone.roll = -3.1416
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['spine']]
+ bones['ribs'] = bone.name
+ bone = arm.edit_bones.new('shin.L')
+ bone.head[:] = 0.0980, -0.0286, 0.5372
+ bone.tail[:] = 0.0980, 0.0162, 0.0852
+ bone.roll = 0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['thigh.L']]
+ bones['shin.L'] = bone.name
+ bone = arm.edit_bones.new('shin.R')
+ bone.head[:] = -0.0980, -0.0286, 0.5372
+ bone.tail[:] = -0.0980, 0.0162, 0.0852
+ bone.roll = 0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['thigh.R']]
+ bones['shin.R'] = bone.name
+ bone = arm.edit_bones.new('neck')
+ bone.head[:] = 0.0000, 0.0114, 1.6582
+ bone.tail[:] = 0.0000, -0.0247, 1.7813
+ bone.roll = 3.1416
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['ribs']]
+ bones['neck'] = bone.name
+ bone = arm.edit_bones.new('shoulder.L')
+ bone.head[:] = 0.0183, -0.0684, 1.6051
+ bone.tail[:] = 0.1694, 0.0205, 1.6050
+ bone.roll = 0.0004
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['ribs']]
+ bones['shoulder.L'] = bone.name
+ bone = arm.edit_bones.new('shoulder.R')
+ bone.head[:] = -0.0183, -0.0684, 1.6051
+ bone.tail[:] = -0.1694, 0.0205, 1.6050
+ bone.roll = -0.0004
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['ribs']]
+ bones['shoulder.R'] = bone.name
+ bone = arm.edit_bones.new('foot.L')
+ bone.head[:] = 0.0980, 0.0162, 0.0852
+ bone.tail[:] = 0.0980, -0.0934, 0.0167
+ bone.roll = 0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['shin.L']]
+ bones['foot.L'] = bone.name
+ bone = arm.edit_bones.new('heel.L')
+ bone.head[:] = 0.0980, 0.0162, 0.0852
+ bone.tail[:] = 0.0980, 0.0882, -0.0000
+ bone.roll = -3.1416
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['shin.L']]
+ bones['heel.L'] = bone.name
+ bone = arm.edit_bones.new('foot.R')
+ bone.head[:] = -0.0980, 0.0162, 0.0852
+ bone.tail[:] = -0.0980, -0.0934, 0.0167
+ bone.roll = -0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['shin.R']]
+ bones['foot.R'] = bone.name
+ bone = arm.edit_bones.new('heel.R')
+ bone.head[:] = -0.0980, 0.0162, 0.0852
+ bone.tail[:] = -0.0980, 0.0882, -0.0000
+ bone.roll = 3.1416
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['shin.R']]
+ bones['heel.R'] = bone.name
+ bone = arm.edit_bones.new('head')
+ bone.head[:] = 0.0000, -0.0247, 1.7813
+ bone.tail[:] = 0.0000, -0.0247, 1.9347
+ bone.roll = 3.1416
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['neck']]
+ bones['head'] = bone.name
+ bone = arm.edit_bones.new('upper_arm.L')
+ bone.head[:] = 0.1953, 0.0267, 1.5846
+ bone.tail[:] = 0.4424, 0.0885, 1.4491
+ bone.roll = 2.0691
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['shoulder.L']]
+ bones['upper_arm.L'] = bone.name
+ bone = arm.edit_bones.new('upper_arm.R')
+ bone.head[:] = -0.1953, 0.0267, 1.5846
+ bone.tail[:] = -0.4424, 0.0885, 1.4491
+ bone.roll = -2.0691
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['shoulder.R']]
+ bones['upper_arm.R'] = bone.name
+ bone = arm.edit_bones.new('toe.L')
+ bone.head[:] = 0.0980, -0.0934, 0.0167
+ bone.tail[:] = 0.0980, -0.1606, 0.0167
+ bone.roll = -0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['foot.L']]
+ bones['toe.L'] = bone.name
+ bone = arm.edit_bones.new('toe.R')
+ bone.head[:] = -0.0980, -0.0934, 0.0167
+ bone.tail[:] = -0.0980, -0.1606, 0.0167
+ bone.roll = 0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['foot.R']]
+ bones['toe.R'] = bone.name
+ bone = arm.edit_bones.new('forearm.L')
+ bone.head[:] = 0.4424, 0.0885, 1.4491
+ bone.tail[:] = 0.6594, 0.0492, 1.3061
+ bone.roll = 2.1459
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['upper_arm.L']]
+ bones['forearm.L'] = bone.name
+ bone = arm.edit_bones.new('forearm.R')
+ bone.head[:] = -0.4424, 0.0885, 1.4491
+ bone.tail[:] = -0.6594, 0.0492, 1.3061
+ bone.roll = -2.1459
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['upper_arm.R']]
+ bones['forearm.R'] = bone.name
+ bone = arm.edit_bones.new('hand.L')
+ bone.head[:] = 0.6594, 0.0492, 1.3061
+ bone.tail[:] = 0.7234, 0.0412, 1.2585
+ bone.roll = -2.4946
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['forearm.L']]
+ bones['hand.L'] = bone.name
+ bone = arm.edit_bones.new('hand.R')
+ bone.head[:] = -0.6594, 0.0492, 1.3061
+ bone.tail[:] = -0.7234, 0.0412, 1.2585
+ bone.roll = 2.4946
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['forearm.R']]
+ bones['hand.R'] = bone.name
+ bone = arm.edit_bones.new('palm.01.L')
+ bone.head[:] = 0.6921, 0.0224, 1.2882
+ bone.tail[:] = 0.7464, 0.0051, 1.2482
+ bone.roll = -2.4928
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hand.L']]
+ bones['palm.01.L'] = bone.name
+ bone = arm.edit_bones.new('palm.02.L')
+ bone.head[:] = 0.6970, 0.0389, 1.2877
+ bone.tail[:] = 0.7518, 0.0277, 1.2487
+ bone.roll = -2.5274
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hand.L']]
+ bones['palm.02.L'] = bone.name
+ bone = arm.edit_bones.new('palm.03.L')
+ bone.head[:] = 0.6963, 0.0545, 1.2874
+ bone.tail[:] = 0.7540, 0.0521, 1.2482
+ bone.roll = -2.5843
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hand.L']]
+ bones['palm.03.L'] = bone.name
+ bone = arm.edit_bones.new('palm.04.L')
+ bone.head[:] = 0.6929, 0.0696, 1.2871
+ bone.tail[:] = 0.7528, 0.0763, 1.2428
+ bone.roll = -2.5155
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hand.L']]
+ bones['palm.04.L'] = bone.name
+ bone = arm.edit_bones.new('palm.01.R')
+ bone.head[:] = -0.6921, 0.0224, 1.2882
+ bone.tail[:] = -0.7464, 0.0051, 1.2482
+ bone.roll = 2.4928
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hand.R']]
+ bones['palm.01.R'] = bone.name
+ bone = arm.edit_bones.new('palm.02.R')
+ bone.head[:] = -0.6970, 0.0389, 1.2877
+ bone.tail[:] = -0.7518, 0.0277, 1.2487
+ bone.roll = 2.5274
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hand.R']]
+ bones['palm.02.R'] = bone.name
+ bone = arm.edit_bones.new('palm.03.R')
+ bone.head[:] = -0.6963, 0.0544, 1.2874
+ bone.tail[:] = -0.7540, 0.0521, 1.2482
+ bone.roll = 2.5843
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hand.R']]
+ bones['palm.03.R'] = bone.name
+ bone = arm.edit_bones.new('palm.04.R')
+ bone.head[:] = -0.6929, 0.0696, 1.2871
+ bone.tail[:] = -0.7528, 0.0763, 1.2428
+ bone.roll = 2.5155
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['hand.R']]
+ bones['palm.04.R'] = bone.name
+ bone = arm.edit_bones.new('finger_index.01.L')
+ bone.head[:] = 0.7464, 0.0051, 1.2482
+ bone.tail[:] = 0.7718, 0.0013, 1.2112
+ bone.roll = -2.0315
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['palm.01.L']]
+ bones['finger_index.01.L'] = bone.name
+ bone = arm.edit_bones.new('thumb.01.L')
+ bone.head[:] = 0.6705, 0.0214, 1.2738
+ bone.tail[:] = 0.6857, 0.0015, 1.2404
+ bone.roll = -0.1587
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['palm.01.L']]
+ bones['thumb.01.L'] = bone.name
+ bone = arm.edit_bones.new('finger_middle.01.L')
+ bone.head[:] = 0.7518, 0.0277, 1.2487
+ bone.tail[:] = 0.7762, 0.0234, 1.2058
+ bone.roll = -2.0067
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['palm.02.L']]
+ bones['finger_middle.01.L'] = bone.name
+ bone = arm.edit_bones.new('finger_ring.01.L')
+ bone.head[:] = 0.7540, 0.0521, 1.2482
+ bone.tail[:] = 0.7715, 0.0499, 1.2070
+ bone.roll = -2.0082
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['palm.03.L']]
+ bones['finger_ring.01.L'] = bone.name
+ bone = arm.edit_bones.new('finger_pinky.01.L')
+ bone.head[:] = 0.7528, 0.0763, 1.2428
+ bone.tail[:] = 0.7589, 0.0765, 1.2156
+ bone.roll = -1.9749
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['palm.04.L']]
+ bones['finger_pinky.01.L'] = bone.name
+ bone = arm.edit_bones.new('finger_index.01.R')
+ bone.head[:] = -0.7464, 0.0051, 1.2482
+ bone.tail[:] = -0.7718, 0.0012, 1.2112
+ bone.roll = 2.0315
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['palm.01.R']]
+ bones['finger_index.01.R'] = bone.name
+ bone = arm.edit_bones.new('thumb.01.R')
+ bone.head[:] = -0.6705, 0.0214, 1.2738
+ bone.tail[:] = -0.6857, 0.0015, 1.2404
+ bone.roll = 0.1587
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['palm.01.R']]
+ bones['thumb.01.R'] = bone.name
+ bone = arm.edit_bones.new('finger_middle.01.R')
+ bone.head[:] = -0.7518, 0.0277, 1.2487
+ bone.tail[:] = -0.7762, 0.0233, 1.2058
+ bone.roll = 2.0067
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['palm.02.R']]
+ bones['finger_middle.01.R'] = bone.name
+ bone = arm.edit_bones.new('finger_ring.01.R')
+ bone.head[:] = -0.7540, 0.0521, 1.2482
+ bone.tail[:] = -0.7715, 0.0499, 1.2070
+ bone.roll = 2.0082
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['palm.03.R']]
+ bones['finger_ring.01.R'] = bone.name
+ bone = arm.edit_bones.new('finger_pinky.01.R')
+ bone.head[:] = -0.7528, 0.0763, 1.2428
+ bone.tail[:] = -0.7589, 0.0765, 1.2156
+ bone.roll = 1.9749
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['palm.04.R']]
+ bones['finger_pinky.01.R'] = bone.name
+ bone = arm.edit_bones.new('finger_index.02.L')
+ bone.head[:] = 0.7718, 0.0013, 1.2112
+ bone.tail[:] = 0.7840, -0.0003, 1.1858
+ bone.roll = -1.8799
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_index.01.L']]
+ bones['finger_index.02.L'] = bone.name
+ bone = arm.edit_bones.new('thumb.02.L')
+ bone.head[:] = 0.6857, 0.0015, 1.2404
+ bone.tail[:] = 0.7056, -0.0057, 1.2145
+ bone.roll = -0.4798
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['thumb.01.L']]
+ bones['thumb.02.L'] = bone.name
+ bone = arm.edit_bones.new('finger_middle.02.L')
+ bone.head[:] = 0.7762, 0.0234, 1.2058
+ bone.tail[:] = 0.7851, 0.0218, 1.1749
+ bone.roll = -1.8283
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_middle.01.L']]
+ bones['finger_middle.02.L'] = bone.name
+ bone = arm.edit_bones.new('finger_ring.02.L')
+ bone.head[:] = 0.7715, 0.0499, 1.2070
+ bone.tail[:] = 0.7794, 0.0494, 1.1762
+ bone.roll = -1.8946
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_ring.01.L']]
+ bones['finger_ring.02.L'] = bone.name
+ bone = arm.edit_bones.new('finger_pinky.02.L')
+ bone.head[:] = 0.7589, 0.0765, 1.2156
+ bone.tail[:] = 0.7618, 0.0770, 1.1932
+ bone.roll = -1.9059
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_pinky.01.L']]
+ bones['finger_pinky.02.L'] = bone.name
+ bone = arm.edit_bones.new('finger_index.02.R')
+ bone.head[:] = -0.7718, 0.0012, 1.2112
+ bone.tail[:] = -0.7840, -0.0003, 1.1858
+ bone.roll = 1.8799
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_index.01.R']]
+ bones['finger_index.02.R'] = bone.name
+ bone = arm.edit_bones.new('thumb.02.R')
+ bone.head[:] = -0.6857, 0.0015, 1.2404
+ bone.tail[:] = -0.7056, -0.0057, 1.2145
+ bone.roll = 0.4798
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['thumb.01.R']]
+ bones['thumb.02.R'] = bone.name
+ bone = arm.edit_bones.new('finger_middle.02.R')
+ bone.head[:] = -0.7762, 0.0233, 1.2058
+ bone.tail[:] = -0.7851, 0.0218, 1.1749
+ bone.roll = 1.8283
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_middle.01.R']]
+ bones['finger_middle.02.R'] = bone.name
+ bone = arm.edit_bones.new('finger_ring.02.R')
+ bone.head[:] = -0.7715, 0.0499, 1.2070
+ bone.tail[:] = -0.7794, 0.0494, 1.1762
+ bone.roll = 1.8946
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_ring.01.R']]
+ bones['finger_ring.02.R'] = bone.name
+ bone = arm.edit_bones.new('finger_pinky.02.R')
+ bone.head[:] = -0.7589, 0.0765, 1.2156
+ bone.tail[:] = -0.7618, 0.0770, 1.1932
+ bone.roll = 1.9059
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_pinky.01.R']]
+ bones['finger_pinky.02.R'] = bone.name
+ bone = arm.edit_bones.new('finger_index.03.L')
+ bone.head[:] = 0.7840, -0.0003, 1.1858
+ bone.tail[:] = 0.7892, 0.0006, 1.1636
+ bone.roll = -1.6760
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_index.02.L']]
+ bones['finger_index.03.L'] = bone.name
+ bone = arm.edit_bones.new('thumb.03.L')
+ bone.head[:] = 0.7056, -0.0057, 1.2145
+ bone.tail[:] = 0.7194, -0.0098, 1.1995
+ bone.roll = -0.5826
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['thumb.02.L']]
+ bones['thumb.03.L'] = bone.name
+ bone = arm.edit_bones.new('finger_middle.03.L')
+ bone.head[:] = 0.7851, 0.0218, 1.1749
+ bone.tail[:] = 0.7888, 0.0216, 1.1525
+ bone.roll = -1.7483
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_middle.02.L']]
+ bones['finger_middle.03.L'] = bone.name
+ bone = arm.edit_bones.new('finger_ring.03.L')
+ bone.head[:] = 0.7794, 0.0494, 1.1762
+ bone.tail[:] = 0.7781, 0.0498, 1.1577
+ bone.roll = -1.6582
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_ring.02.L']]
+ bones['finger_ring.03.L'] = bone.name
+ bone = arm.edit_bones.new('finger_pinky.03.L')
+ bone.head[:] = 0.7618, 0.0770, 1.1932
+ bone.tail[:] = 0.7611, 0.0772, 1.1782
+ bone.roll = -1.7639
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_pinky.02.L']]
+ bones['finger_pinky.03.L'] = bone.name
+ bone = arm.edit_bones.new('finger_index.03.R')
+ bone.head[:] = -0.7840, -0.0003, 1.1858
+ bone.tail[:] = -0.7892, 0.0006, 1.1636
+ bone.roll = 1.6760
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_index.02.R']]
+ bones['finger_index.03.R'] = bone.name
+ bone = arm.edit_bones.new('thumb.03.R')
+ bone.head[:] = -0.7056, -0.0057, 1.2145
+ bone.tail[:] = -0.7194, -0.0098, 1.1995
+ bone.roll = 0.5826
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['thumb.02.R']]
+ bones['thumb.03.R'] = bone.name
+ bone = arm.edit_bones.new('finger_middle.03.R')
+ bone.head[:] = -0.7851, 0.0218, 1.1749
+ bone.tail[:] = -0.7888, 0.0216, 1.1525
+ bone.roll = 1.7483
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_middle.02.R']]
+ bones['finger_middle.03.R'] = bone.name
+ bone = arm.edit_bones.new('finger_ring.03.R')
+ bone.head[:] = -0.7794, 0.0494, 1.1762
+ bone.tail[:] = -0.7781, 0.0498, 1.1577
+ bone.roll = 1.6582
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_ring.02.R']]
+ bones['finger_ring.03.R'] = bone.name
+ bone = arm.edit_bones.new('finger_pinky.03.R')
+ bone.head[:] = -0.7618, 0.0770, 1.1932
+ bone.tail[:] = -0.7611, 0.0772, 1.1782
+ bone.roll = 1.7639
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger_pinky.02.R']]
+ bones['finger_pinky.03.R'] = bone.name
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bones['hips']]
+ pbone.rigify_type = 'spine'
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [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]
+ pbone = obj.pose.bones[bones['spine']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [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]
+ pbone = obj.pose.bones[bones['thigh.L']]
+ pbone.rigify_type = 'biped.leg'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_ik_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].ik_layers = [False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['thigh.R']]
+ pbone.rigify_type = 'biped.leg'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_ik_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].ik_layers = [False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['ribs']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [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]
+ pbone = obj.pose.bones[bones['shin.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['shin.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['neck']]
+ pbone.rigify_type = 'neck_short'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, 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]
+ pbone = obj.pose.bones[bones['shoulder.L']]
+ pbone.rigify_type = 'copy'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, True, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [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]
+ pbone = obj.pose.bones[bones['shoulder.R']]
+ pbone.rigify_type = 'copy'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, True, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [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]
+ pbone = obj.pose.bones[bones['foot.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['heel.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['foot.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['heel.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['head']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, 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]
+ pbone = obj.pose.bones[bones['upper_arm.L']]
+ pbone.rigify_type = 'biped.arm'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_ik_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].ik_layers = [False, False, False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['upper_arm.R']]
+ pbone.rigify_type = 'biped.arm'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_ik_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].ik_layers = [False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['toe.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['toe.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['forearm.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['forearm.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['hand.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['hand.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
+ pbone = obj.pose.bones[bones['palm.01.L']]
+ pbone.rigify_type = 'palm'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ pbone = obj.pose.bones[bones['palm.02.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['palm.03.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['palm.04.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['palm.01.R']]
+ pbone.rigify_type = 'palm'
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ pbone = obj.pose.bones[bones['palm.02.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['palm.03.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['palm.04.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_index.01.L']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['thumb.01.L']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['finger_middle.01.L']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['finger_ring.01.L']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['finger_pinky.01.L']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['finger_index.01.R']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['thumb.01.R']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['finger_middle.01.R']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['finger_ring.01.R']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['finger_pinky.01.R']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone.rigify_parameters.add()
+ try:
+ pbone.rigify_parameters[0].separate_extra_layers = True
+ except AttributeError:
+ pass
+ try:
+ pbone.rigify_parameters[0].extra_layers = [False, False, False, False, False, 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]
+ except AttributeError:
+ pass
+ pbone = obj.pose.bones[bones['finger_index.02.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['thumb.02.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_middle.02.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_ring.02.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_pinky.02.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_index.02.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['thumb.02.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_middle.02.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_ring.02.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_pinky.02.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_index.03.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['thumb.03.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_middle.03.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_ring.03.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_pinky.03.L']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_index.03.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['thumb.03.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_middle.03.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_ring.03.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+ pbone = obj.pose.bones[bones['finger_pinky.03.R']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.bone.layers = [False, False, False, False, 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]
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in arm.edit_bones:
+ bone.select = False
+ bone.select_head = False
+ bone.select_tail = False
+ for b in bones:
+ bone = arm.edit_bones[bones[b]]
+ bone.select = True
+ bone.select_head = True
+ bone.select_tail = True
+ arm.edit_bones.active = bone
+
+ arm.layers = [(x in [0, 2, 4, 6, 8, 10, 12]) for x in range(0, 32)]
+
diff --git a/rigify/rig_ui_template.py b/rigify/rig_ui_template.py
new file mode 100644
index 00000000..82d853a4
--- /dev/null
+++ b/rigify/rig_ui_template.py
@@ -0,0 +1,96 @@
+#====================== 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 ========================
+
+UI_SLIDERS = """
+import bpy
+
+rig_id = "%s"
+
+
+class RigUI(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_label = "Rig Main Properties"
+ bl_idname = rig_id + "_PT_rig_ui"
+
+ @classmethod
+ def poll(self, context):
+ if context.mode != 'POSE':
+ return False
+ try:
+ return (context.active_object.data.get("rig_id") == rig_id)
+ except (AttributeError, KeyError, TypeError):
+ return False
+
+ def draw(self, context):
+ layout = self.layout
+ pose_bones = context.active_object.pose.bones
+ try:
+ selected_bones = [bone.name for bone in context.selected_pose_bones]
+ selected_bones += [context.active_pose_bone.name]
+ except (AttributeError, TypeError):
+ return
+
+ def is_selected(names):
+ # Returns whether any of the named bones are selected.
+ if type(names) == list:
+ for name in names:
+ if name in selected_bones:
+ return True
+ elif names in selected_bones:
+ return True
+ return False
+
+
+"""
+
+
+def layers_ui(layers):
+ """ Turn a list of booleans into a layer UI.
+ """
+
+ code = """
+class RigLayers(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'UI'
+ bl_label = "Rig Layers"
+ bl_idname = rig_id + "_PT_rig_layers"
+
+ @classmethod
+ def poll(self, context):
+ try:
+ return (context.active_object.data.get("rig_id") == rig_id)
+ except (AttributeError, KeyError, TypeError):
+ return False
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column()
+"""
+ i = 0
+ for layer in layers:
+ if layer:
+ code += "\n row = col.row()\n"
+ if i == 28:
+ code += " row.prop(context.active_object.data, 'layers', index=%s, toggle=True, text='Root')\n" % (str(i))
+ else:
+ code += " row.prop(context.active_object.data, 'layers', index=%s, toggle=True, text='%s')\n" % (str(i), str(i + 1))
+ i += 1
+
+ return code
+
diff --git a/rigify/rigs/__init__.py b/rigify/rigs/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/rigify/rigs/__init__.py
diff --git a/rigify/rigs/biped/__init__.py b/rigify/rigs/biped/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/rigify/rigs/biped/__init__.py
diff --git a/rigify/rigs/biped/arm/__init__.py b/rigify/rigs/biped/arm/__init__.py
new file mode 100644
index 00000000..13d9e430
--- /dev/null
+++ b/rigify/rigs/biped/arm/__init__.py
@@ -0,0 +1,209 @@
+#====================== 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
+import imp
+from . import fk, ik, deform
+from rigify.utils import MetarigError, get_layers
+
+imp.reload(fk)
+imp.reload(ik)
+imp.reload(deform)
+
+script = """
+fk_arm = ["%s", "%s", "%s"]
+ik_arm = ["%s", "%s"]
+if is_selected(fk_arm+ik_arm):
+ layout.prop(pose_bones[ik_arm[0]], '["ikfk_switch"]', text="FK / IK (" + ik_arm[0] + ")", slider=True)
+if is_selected(fk_arm):
+ layout.prop(pose_bones[fk_arm[0]], '["isolate"]', text="Isolate Rotation (" + fk_arm[0] + ")", slider=True)
+"""
+
+
+class Rig:
+ """ An arm rig, with IK/FK switching and hinge switch.
+
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ Store any data or references to data that will be needed later on.
+ In particular, store names of bones that will be needed.
+ Do NOT change any data in the scene. This is a gathering phase only.
+
+ """
+ # Gather deform rig
+ self.deform_rig = deform.Rig(obj, bone, params)
+
+ # Gather FK rig
+ self.fk_rig = fk.Rig(obj, bone, params)
+
+ # Gather IK rig
+ self.ik_rig = ik.Rig(obj, bone, params, ikfk_switch=True)
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ self.deform_rig.generate()
+ fk_controls = self.fk_rig.generate()
+ ik_controls = self.ik_rig.generate()
+ return [script % (fk_controls[0], fk_controls[1], fk_controls[2], ik_controls[0], ik_controls[1])]
+
+ @classmethod
+ def add_parameters(self, group):
+ """ Add the parameters of this rig type to the
+ RigifyParameters IDPropertyGroup
+
+ """
+ items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')]
+ group.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X')
+
+ group.separate_ik_layers = bpy.props.BoolProperty(name="Separate IK Control Layers:", default=False, description="Enable putting the ik controls on a separate layer from the fk controls.")
+ group.ik_layers = bpy.props.BoolVectorProperty(size=32, description="Layers for the ik controls to be on.")
+
+ group.use_upper_arm_twist = bpy.props.BoolProperty(name="Upper Arm Twist", default=True, description="Generate the dual-bone twist setup for the upper arm.")
+ group.use_forearm_twist = bpy.props.BoolProperty(name="Forearm Twist", default=True, description="Generate the dual-bone twist setup for the forearm.")
+
+ @classmethod
+ def parameters_ui(self, layout, obj, bone):
+ """ Create the ui for the rig parameters.
+
+ """
+ params = obj.pose.bones[bone].rigify_parameters[0]
+
+ r = layout.row()
+ r.prop(params, "separate_ik_layers")
+
+ r = layout.row()
+ r.active = params.separate_ik_layers
+
+ col = r.column(align=True)
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=0, toggle=True, text="")
+ row.prop(params, "ik_layers", index=1, toggle=True, text="")
+ row.prop(params, "ik_layers", index=2, toggle=True, text="")
+ row.prop(params, "ik_layers", index=3, toggle=True, text="")
+ row.prop(params, "ik_layers", index=4, toggle=True, text="")
+ row.prop(params, "ik_layers", index=5, toggle=True, text="")
+ row.prop(params, "ik_layers", index=6, toggle=True, text="")
+ row.prop(params, "ik_layers", index=7, toggle=True, text="")
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=16, toggle=True, text="")
+ row.prop(params, "ik_layers", index=17, toggle=True, text="")
+ row.prop(params, "ik_layers", index=18, toggle=True, text="")
+ row.prop(params, "ik_layers", index=19, toggle=True, text="")
+ row.prop(params, "ik_layers", index=20, toggle=True, text="")
+ row.prop(params, "ik_layers", index=21, toggle=True, text="")
+ row.prop(params, "ik_layers", index=22, toggle=True, text="")
+ row.prop(params, "ik_layers", index=23, toggle=True, text="")
+
+ col = r.column(align=True)
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=8, toggle=True, text="")
+ row.prop(params, "ik_layers", index=9, toggle=True, text="")
+ row.prop(params, "ik_layers", index=10, toggle=True, text="")
+ row.prop(params, "ik_layers", index=11, toggle=True, text="")
+ row.prop(params, "ik_layers", index=12, toggle=True, text="")
+ row.prop(params, "ik_layers", index=13, toggle=True, text="")
+ row.prop(params, "ik_layers", index=14, toggle=True, text="")
+ row.prop(params, "ik_layers", index=15, toggle=True, text="")
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=24, toggle=True, text="")
+ row.prop(params, "ik_layers", index=25, toggle=True, text="")
+ row.prop(params, "ik_layers", index=26, toggle=True, text="")
+ row.prop(params, "ik_layers", index=27, toggle=True, text="")
+ row.prop(params, "ik_layers", index=28, toggle=True, text="")
+ row.prop(params, "ik_layers", index=29, toggle=True, text="")
+ row.prop(params, "ik_layers", index=30, toggle=True, text="")
+ row.prop(params, "ik_layers", index=31, toggle=True, text="")
+
+ r = layout.row()
+ r.label(text="Elbow rotation axis:")
+ r.prop(params, "primary_rotation_axis", text="")
+
+ col = layout.column()
+ col.prop(params, "use_upper_arm_twist")
+ col.prop(params, "use_forearm_twist")
+
+ @classmethod
+ def create_sample(self, obj):
+ # generated by rigify.utils.write_meta_rig
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bones = {}
+
+ bone = arm.edit_bones.new('upper_arm')
+ bone.head[:] = 0.0000, 0.0000, 0.0000
+ bone.tail[:] = 0.3000, 0.0300, 0.0000
+ bone.roll = 1.5708
+ bone.use_connect = False
+ bones['upper_arm'] = bone.name
+ bone = arm.edit_bones.new('forearm')
+ bone.head[:] = 0.3000, 0.0300, 0.0000
+ bone.tail[:] = 0.6000, 0.0000, 0.0000
+ bone.roll = 1.5708
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['upper_arm']]
+ bones['forearm'] = bone.name
+ bone = arm.edit_bones.new('hand')
+ bone.head[:] = 0.6000, 0.0000, 0.0000
+ bone.tail[:] = 0.7000, 0.0000, 0.0000
+ bone.roll = 3.1416
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['forearm']]
+ bones['hand'] = bone.name
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bones['upper_arm']]
+ pbone.rigify_type = 'biped.arm'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.rigify_parameters.add()
+ pbone = obj.pose.bones[bones['forearm']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone = obj.pose.bones[bones['hand']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in arm.edit_bones:
+ bone.select = False
+ bone.select_head = False
+ bone.select_tail = False
+ for b in bones:
+ bone = arm.edit_bones[bones[b]]
+ bone.select = True
+ bone.select_head = True
+ bone.select_tail = True
+ arm.edit_bones.active = bone
+
diff --git a/rigify/rigs/biped/arm/deform.py b/rigify/rigs/biped/arm/deform.py
new file mode 100644
index 00000000..2a7b3109
--- /dev/null
+++ b/rigify/rigs/biped/arm/deform.py
@@ -0,0 +1,230 @@
+#====================== 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 math import acos, degrees
+from mathutils import Vector, Matrix
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone, flip_bone, put_bone
+from rigify.utils import connected_children_names
+from rigify.utils import strip_org, make_mechanism_name, make_deformer_name
+
+
+def align_roll(obj, bone1, bone2):
+ bone1_e = obj.data.edit_bones[bone1]
+ bone2_e = obj.data.edit_bones[bone2]
+
+ bone1_e.roll = 0.0
+
+ # Get the directions the bones are pointing in, as vectors
+ y1 = bone1_e.y_axis
+ x1 = bone1_e.x_axis
+ y2 = bone2_e.y_axis
+ x2 = bone2_e.x_axis
+
+ # Get the shortest axis to rotate bone1 on to point in the same direction as bone2
+ axis = y1.cross(y2)
+ axis.normalize()
+
+ # Angle to rotate on that shortest axis
+ angle = y1.angle(y2)
+
+ # Create rotation matrix to make bone1 point in the same direction as bone2
+ rot_mat = Matrix.Rotation(angle, 3, axis)
+
+ # Roll factor
+ x3 = x1 * rot_mat
+ dot = x2 * x3
+ if dot > 1.0:
+ dot = 1.0
+ elif dot < -1.0:
+ dot = -1.0
+ roll = acos(dot)
+
+ # Set the roll
+ bone1_e.roll = roll
+
+ # Check if we rolled in the right direction
+ x3 = bone1_e.x_axis * rot_mat
+ check = x2 * x3
+
+ # If not, reverse
+ if check < 0.9999:
+ bone1_e.roll = -roll
+
+
+class Rig:
+ """ An FK arm rig, with hinge switch.
+
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ Store any data or references to data that will be needed later on.
+ In particular, store references to bones that will be needed, and
+ store names of bones that will be needed.
+ Do NOT change any data in the scene. This is a gathering phase only.
+
+ """
+ self.obj = obj
+ self.params = params
+
+ # Get the chain of 3 connected bones
+ self.org_bones = [bone] + connected_children_names(self.obj, bone)[:2]
+
+ if len(self.org_bones) != 3:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 3 bones." % (strip_org(bone)))
+
+ # Get rig parameters
+ self.use_upper_arm_twist = params.use_upper_arm_twist
+ self.use_forearm_twist = params.use_forearm_twist
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Create upper arm bones
+ if self.use_upper_arm_twist:
+ uarm1 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".01")))
+ uarm2 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".02")))
+ utip = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[0] + ".tip")))
+ else:
+ uarm = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0])))
+
+ # Create forearm bones
+ if self.use_forearm_twist:
+ farm1 = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1] + ".01")))
+ farm2 = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1] + ".02")))
+ ftip = copy_bone(self.obj, self.org_bones[1], make_mechanism_name(strip_org(self.org_bones[1] + ".tip")))
+ else:
+ farm = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1])))
+
+ # Create hand bone
+ hand = copy_bone(self.obj, self.org_bones[2], make_deformer_name(strip_org(self.org_bones[2])))
+
+ # Get edit bones
+ eb = self.obj.data.edit_bones
+
+ org_uarm_e = eb[self.org_bones[0]]
+ if self.use_upper_arm_twist:
+ uarm1_e = eb[uarm1]
+ uarm2_e = eb[uarm2]
+ utip_e = eb[utip]
+ else:
+ uarm_e = eb[uarm]
+
+ org_farm_e = eb[self.org_bones[1]]
+ if self.use_forearm_twist:
+ farm1_e = eb[farm1]
+ farm2_e = eb[farm2]
+ ftip_e = eb[ftip]
+ else:
+ farm_e = eb[farm]
+
+ org_hand_e = eb[self.org_bones[2]]
+ hand_e = eb[hand]
+
+ # Parent and position upper arm bones
+ if self.use_upper_arm_twist:
+ uarm1_e.use_connect = False
+ uarm2_e.use_connect = False
+ utip_e.use_connect = False
+
+ uarm1_e.parent = org_uarm_e.parent
+ uarm2_e.parent = org_uarm_e
+ utip_e.parent = org_uarm_e
+
+ center = Vector((org_uarm_e.head + org_uarm_e.tail) / 2)
+
+ uarm1_e.tail = center
+ uarm2_e.head = center
+ put_bone(self.obj, utip, org_uarm_e.tail)
+ utip_e.length = org_uarm_e.length / 8
+ else:
+ uarm_e.use_connect = False
+ uarm_e.parent = org_uarm_e
+
+ # Parent and position forearm bones
+ if self.use_forearm_twist:
+ farm1_e.use_connect = False
+ farm2_e.use_connect = False
+ ftip_e.use_connect = False
+
+ farm1_e.parent = org_farm_e
+ farm2_e.parent = org_farm_e
+ ftip_e.parent = org_farm_e
+
+ center = Vector((org_farm_e.head + org_farm_e.tail) / 2)
+
+ farm1_e.tail = center
+ farm2_e.head = center
+ put_bone(self.obj, ftip, org_farm_e.tail)
+ ftip_e.length = org_farm_e.length / 8
+
+ # Align roll of farm2 with hand
+ align_roll(self.obj, farm2, hand)
+ else:
+ farm_e.use_connect = False
+ farm_e.parent = org_farm_e
+
+ # Parent hand
+ hand_e.use_connect = False
+ hand_e.parent = org_hand_e
+
+ # Object mode, get pose bones
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ if self.use_upper_arm_twist:
+ uarm1_p = pb[uarm1]
+ if self.use_forearm_twist:
+ farm2_p = pb[farm2]
+ hand_p = pb[hand]
+
+ # Upper arm constraints
+ if self.use_upper_arm_twist:
+ con = uarm1_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = self.org_bones[0]
+
+ con = uarm1_p.constraints.new('COPY_SCALE')
+ con.name = "copy_scale"
+ con.target = self.obj
+ con.subtarget = self.org_bones[0]
+
+ con = uarm1_p.constraints.new('DAMPED_TRACK')
+ con.name = "track_to"
+ con.target = self.obj
+ con.subtarget = utip
+
+ # Forearm constraints
+ if self.use_forearm_twist:
+ con = farm2_p.constraints.new('COPY_ROTATION')
+ con.name = "copy_rotation"
+ con.target = self.obj
+ con.subtarget = hand
+
+ con = farm2_p.constraints.new('DAMPED_TRACK')
+ con.name = "track_to"
+ con.target = self.obj
+ con.subtarget = ftip
+
diff --git a/rigify/rigs/biped/arm/fk.py b/rigify/rigs/biped/arm/fk.py
new file mode 100644
index 00000000..20ba89f2
--- /dev/null
+++ b/rigify/rigs/biped/arm/fk.py
@@ -0,0 +1,208 @@
+#====================== BEGIN GPL LICENSE BLOCK ======================
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+#======================= END GPL LICENSE BLOCK ========================
+
+import bpy
+import math
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone, flip_bone, put_bone
+from rigify.utils import connected_children_names
+from rigify.utils import strip_org, make_mechanism_name, make_deformer_name
+from rigify.utils import get_layers
+from rigify.utils import create_widget, create_limb_widget
+from rna_prop_ui import rna_idprop_ui_prop_get
+
+
+class Rig:
+ """ An FK arm rig, with hinge switch.
+
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ Store any data or references to data that will be needed later on.
+ In particular, store references to bones that will be needed, and
+ store names of bones that will be needed.
+ Do NOT change any data in the scene. This is a gathering phase only.
+
+ """
+ self.obj = obj
+ self.params = params
+
+ # Get the chain of 3 connected bones
+ self.org_bones = [bone] + connected_children_names(self.obj, bone)[:2]
+
+ if len(self.org_bones) != 3:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 3 bones." % (strip_org(bone)))
+
+ # Get (optional) parent
+ if self.obj.data.bones[bone].parent == None:
+ self.org_parent = None
+ else:
+ self.org_parent = self.obj.data.bones[bone].parent.name
+
+ # Get the rig parameters
+ if "layers" in params:
+ self.layers = get_layers(params["layers"])
+ else:
+ self.layers = None
+
+ self.primary_rotation_axis = params.primary_rotation_axis
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Create the control bones
+ uarm = copy_bone(self.obj, self.org_bones[0], strip_org(self.org_bones[0]))
+ farm = copy_bone(self.obj, self.org_bones[1], strip_org(self.org_bones[1]))
+ hand = copy_bone(self.obj, self.org_bones[2], strip_org(self.org_bones[2]))
+
+ # Create the hinge bones
+ if self.org_parent != None:
+ hinge = copy_bone(self.obj, self.org_parent, make_mechanism_name(uarm + ".hinge"))
+ socket1 = copy_bone(self.obj, uarm, make_mechanism_name(uarm + ".socket1"))
+ socket2 = copy_bone(self.obj, uarm, make_mechanism_name(uarm + ".socket2"))
+
+ # Get edit bones
+ eb = self.obj.data.edit_bones
+
+ uarm_e = eb[uarm]
+ farm_e = eb[farm]
+ hand_e = eb[hand]
+
+ if self.org_parent != None:
+ hinge_e = eb[hinge]
+ socket1_e = eb[socket1]
+ socket2_e = eb[socket2]
+
+ # Parenting
+ farm_e.parent = uarm_e
+ hand_e.parent = farm_e
+
+ if self.org_parent != None:
+ hinge_e.use_connect = False
+ socket1_e.use_connect = False
+ socket2_e.use_connect = False
+
+ uarm_e.parent = hinge_e
+ hinge_e.parent = socket2_e
+ socket2_e.parent = None
+
+ # Positioning
+ if self.org_parent != None:
+ center = (hinge_e.head + hinge_e.tail) / 2
+ hinge_e.head = center
+ socket1_e.length /= 4
+ socket2_e.length /= 3
+
+ # Object mode, get pose bones
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ uarm_p = pb[uarm]
+ farm_p = pb[farm]
+ hand_p = pb[hand]
+
+ if self.org_parent != None:
+ socket1_p = pb[socket1]
+ socket2_p = pb[socket2]
+
+ # Set the elbow to only bend on the x-axis.
+ farm_p.rotation_mode = 'XYZ'
+ if 'X' in self.primary_rotation_axis:
+ farm_p.lock_rotation = (False, True, True)
+ elif 'Y' in self.primary_rotation_axis:
+ farm_p.lock_rotation = (True, False, True)
+ else:
+ farm_p.lock_rotation = (True, True, False)
+
+ # Set up custom properties
+ if self.org_parent != None:
+ prop = rna_idprop_ui_prop_get(uarm_p, "isolate", create=True)
+ uarm_p["isolate"] = 0.0
+ prop["soft_min"] = prop["min"] = 0.0
+ prop["soft_max"] = prop["max"] = 1.0
+
+ # Hinge constraints / drivers
+ if self.org_parent != None:
+ con = socket2_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = socket1
+
+ con = socket2_p.constraints.new('COPY_TRANSFORMS')
+ con.name = "isolate_off"
+ con.target = self.obj
+ con.subtarget = socket1
+
+ # Driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = uarm_p.path_from_id() + '["isolate"]'
+ mod = fcurve.modifiers[0]
+ mod.poly_order = 1
+ mod.coefficients[0] = 1.0
+ mod.coefficients[1] = -1.0
+
+ # Constrain org bones to controls
+ con = pb[self.org_bones[0]].constraints.new('COPY_TRANSFORMS')
+ con.name = "fk"
+ con.target = self.obj
+ con.subtarget = uarm
+
+ con = pb[self.org_bones[1]].constraints.new('COPY_TRANSFORMS')
+ con.name = "fk"
+ con.target = self.obj
+ con.subtarget = farm
+
+ con = pb[self.org_bones[2]].constraints.new('COPY_TRANSFORMS')
+ con.name = "fk"
+ con.target = self.obj
+ con.subtarget = hand
+
+ # Set layers if specified
+ if self.layers:
+ uarm_p.bone.layers = self.layers
+ farm_p.bone.layers = self.layers
+ hand_p.bone.layers = self.layers
+
+ # Create control widgets
+ create_limb_widget(self.obj, uarm)
+ create_limb_widget(self.obj, farm)
+
+ ob = create_widget(self.obj, hand)
+ if ob != None:
+ verts = [(0.7, 1.5, 0.0), (0.7, -0.25, 0.0), (-0.7, -0.25, 0.0), (-0.7, 1.5, 0.0), (0.7, 0.723, 0.0), (-0.7, 0.723, 0.0), (0.7, 0.0, 0.0), (-0.7, 0.0, 0.0)]
+ edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)]
+ mesh = ob.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+ mod = ob.modifiers.new("subsurf", 'SUBSURF')
+ mod.levels = 2
+
+ return [uarm, farm, hand]
+
diff --git a/rigify/rigs/biped/arm/ik.py b/rigify/rigs/biped/arm/ik.py
new file mode 100644
index 00000000..2b941b9e
--- /dev/null
+++ b/rigify/rigs/biped/arm/ik.py
@@ -0,0 +1,307 @@
+#====================== 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 Vector
+from math import pi, acos
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone
+from rigify.utils import connected_children_names
+from rigify.utils import strip_org, make_mechanism_name, insert_before_lr
+from rigify.utils import get_layers
+from rigify.utils import create_widget, create_line_widget, create_sphere_widget
+from rna_prop_ui import rna_idprop_ui_prop_get
+
+
+def angle_on_plane(plane, vec1, vec2):
+ """ Return the angle between two vectors projected onto a plane.
+ """
+ plane.normalize()
+ vec1 = vec1 - (plane * (vec1.dot(plane)))
+ vec2 = vec2 - (plane * (vec2.dot(plane)))
+ vec1.normalize()
+ vec2.normalize()
+
+ # Determine the angle
+ angle = acos(max(-1.0, min(1.0, vec1.dot(vec2))))
+
+ if angle < 0.00001: # close enough to zero that sign doesn't matter
+ return angle
+
+ # Determine the sign of the angle
+ vec3 = vec2.cross(vec1)
+ vec3.normalize()
+ sign = vec3.dot(plane)
+ if sign >= 0:
+ sign = 1
+ else:
+ sign = -1
+
+ return angle * sign
+
+
+class Rig:
+ """ An IK arm rig, with an optional ik/fk switch.
+
+ """
+ def __init__(self, obj, bone, params, ikfk_switch=False):
+ """ Gather and validate data about the rig.
+ Store any data or references to data that will be needed later on.
+ In particular, store references to bones that will be needed, and
+ store names of bones that will be needed.
+ Do NOT change any data in the scene. This is a gathering phase only.
+
+ ikfk_switch: if True, create an ik/fk switch slider
+ """
+ self.obj = obj
+ self.params = params
+ self.switch = ikfk_switch
+
+ # Get the chain of 3 connected bones
+ self.org_bones = [bone] + connected_children_names(self.obj, bone)[:2]
+
+ if len(self.org_bones) != 3:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 3 bones." % (strip_org(bone)))
+
+ # Get the rig parameters
+ if params.separate_ik_layers:
+ self.layers = list(params.ik_layers)
+ else:
+ self.layers = None
+
+ self.primary_rotation_axis = params.primary_rotation_axis
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Create the bones
+ uarm = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[0], "_ik"))))
+ farm = copy_bone(self.obj, self.org_bones[1], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[1], "_ik"))))
+
+ hand = copy_bone(self.obj, self.org_bones[2], strip_org(insert_before_lr(self.org_bones[2], "_ik")))
+ pole = copy_bone(self.obj, self.org_bones[0], strip_org(insert_before_lr(self.org_bones[0], "_pole")))
+
+ vishand = copy_bone(self.obj, self.org_bones[2], "VIS-" + strip_org(insert_before_lr(self.org_bones[2], "_ik")))
+ vispole = copy_bone(self.obj, self.org_bones[1], "VIS-" + strip_org(insert_before_lr(self.org_bones[0], "_pole")))
+
+ # Get edit bones
+ eb = self.obj.data.edit_bones
+
+ uarm_e = eb[uarm]
+ farm_e = eb[farm]
+ hand_e = eb[hand]
+ pole_e = eb[pole]
+ vishand_e = eb[vishand]
+ vispole_e = eb[vispole]
+
+ # Parenting
+ farm_e.parent = uarm_e
+
+ hand_e.use_connect = False
+ hand_e.parent = None
+
+ pole_e.use_connect = False
+
+ vishand_e.use_connect = False
+ vishand_e.parent = None
+
+ vispole_e.use_connect = False
+ vispole_e.parent = None
+
+ # Misc
+ hand_e.use_local_location = False
+
+ vishand_e.hide_select = True
+ vispole_e.hide_select = True
+
+ # Positioning
+ v1 = farm_e.tail - uarm_e.head
+ if 'X' in self.primary_rotation_axis or 'Y' in self.primary_rotation_axis:
+ v2 = v1.cross(farm_e.x_axis)
+ if (v2 * farm_e.z_axis) > 0.0:
+ v2 *= -1.0
+ else:
+ v2 = v1.cross(farm_e.z_axis)
+ if (v2 * farm_e.x_axis) < 0.0:
+ v2 *= -1.0
+ v2.normalize()
+ v2 *= v1.length
+
+ if '-' in self.primary_rotation_axis:
+ v2 *= -1
+
+ pole_e.head = farm_e.head + v2
+ pole_e.tail = pole_e.head + (Vector((0, 1, 0)) * (v1.length / 8))
+ pole_e.roll = 0.0
+
+ vishand_e.tail = vishand_e.head + Vector((0, 0, v1.length / 32))
+ vispole_e.tail = vispole_e.head + Vector((0, 0, v1.length / 32))
+
+ # Determine the pole offset value
+ plane = (farm_e.tail - uarm_e.head).normalize()
+ vec1 = uarm_e.x_axis.normalize()
+ vec2 = (pole_e.head - uarm_e.head).normalize()
+ pole_offset = angle_on_plane(plane, vec1, vec2)
+
+ # Object mode, get pose bones
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ uarm_p = pb[uarm]
+ farm_p = pb[farm]
+ hand_p = pb[hand]
+ pole_p = pb[pole]
+ vishand_p = pb[vishand]
+ vispole_p = pb[vispole]
+
+ # Set the elbow to only bend on the primary axis
+ if 'X' in self.primary_rotation_axis:
+ farm_p.lock_ik_y = True
+ farm_p.lock_ik_z = True
+ elif 'Y' in self.primary_rotation_axis:
+ farm_p.lock_ik_x = True
+ farm_p.lock_ik_z = True
+ else:
+ farm_p.lock_ik_x = True
+ farm_p.lock_ik_y = True
+
+ # Pole target only translates
+ pole_p.lock_location = False, False, False
+ pole_p.lock_rotation = True, True, True
+ pole_p.lock_rotation_w = True
+ pole_p.lock_scale = True, True, True
+
+ # Set up custom properties
+ if self.switch == True:
+ prop = rna_idprop_ui_prop_get(hand_p, "ikfk_switch", create=True)
+ hand_p["ikfk_switch"] = 0.0
+ prop["soft_min"] = prop["min"] = 0.0
+ prop["soft_max"] = prop["max"] = 1.0
+
+ # IK Constraint
+ con = farm_p.constraints.new('IK')
+ con.name = "ik"
+ con.target = self.obj
+ con.subtarget = hand
+ con.pole_target = self.obj
+ con.pole_subtarget = pole
+ con.pole_angle = pole_offset
+ con.chain_count = 2
+
+ # Constrain org bones to controls
+ con = pb[self.org_bones[0]].constraints.new('COPY_TRANSFORMS')
+ con.name = "ik"
+ con.target = self.obj
+ con.subtarget = uarm
+ if self.switch == True:
+ # IK/FK switch driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = hand_p.path_from_id() + '["ikfk_switch"]'
+
+ con = pb[self.org_bones[1]].constraints.new('COPY_TRANSFORMS')
+ con.name = "ik"
+ con.target = self.obj
+ con.subtarget = farm
+ if self.switch == True:
+ # IK/FK switch driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = hand_p.path_from_id() + '["ikfk_switch"]'
+
+ con = pb[self.org_bones[2]].constraints.new('COPY_TRANSFORMS')
+ con.name = "ik"
+ con.target = self.obj
+ con.subtarget = hand
+ if self.switch == True:
+ # IK/FK switch driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = hand_p.path_from_id() + '["ikfk_switch"]'
+
+ # VIS hand constraints
+ con = vishand_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_loc"
+ con.target = self.obj
+ con.subtarget = self.org_bones[2]
+
+ con = vishand_p.constraints.new('STRETCH_TO')
+ con.name = "stretch_to"
+ con.target = self.obj
+ con.subtarget = hand
+ con.volume = 'NO_VOLUME'
+ con.rest_length = vishand_p.length
+
+ # VIS pole constraints
+ con = vispole_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_loc"
+ con.target = self.obj
+ con.subtarget = self.org_bones[1]
+
+ con = vispole_p.constraints.new('STRETCH_TO')
+ con.name = "stretch_to"
+ con.target = self.obj
+ con.subtarget = pole
+ con.volume = 'NO_VOLUME'
+ con.rest_length = vispole_p.length
+
+ # Set layers if specified
+ if self.layers:
+ hand_p.bone.layers = self.layers
+ pole_p.bone.layers = self.layers
+ vishand_p.bone.layers = self.layers
+ vispole_p.bone.layers = self.layers
+
+ # Create widgets
+ create_line_widget(self.obj, vispole)
+ create_line_widget(self.obj, vishand)
+ create_sphere_widget(self.obj, pole)
+
+ ob = create_widget(self.obj, hand)
+ if ob != None:
+ verts = [(0.7, 1.5, 0.0), (0.7, -0.25, 0.0), (-0.7, -0.25, 0.0), (-0.7, 1.5, 0.0), (0.7, 0.723, 0.0), (-0.7, 0.723, 0.0), (0.7, 0.0, 0.0), (-0.7, 0.0, 0.0)]
+ edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)]
+ mesh = ob.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+ mod = ob.modifiers.new("subsurf", 'SUBSURF')
+ mod.levels = 2
+
+ return [hand, pole]
+
diff --git a/rigify/rigs/biped/leg/__init__.py b/rigify/rigs/biped/leg/__init__.py
new file mode 100644
index 00000000..5cbf3b11
--- /dev/null
+++ b/rigify/rigs/biped/leg/__init__.py
@@ -0,0 +1,237 @@
+#====================== 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
+import imp
+from . import fk, ik, deform
+from rigify.utils import MetarigError, get_layers
+
+imp.reload(fk)
+imp.reload(ik)
+imp.reload(deform)
+
+script = """
+fk_leg = ["%s", "%s", "%s"]
+ik_leg = ["%s", "%s", "%s"]
+if is_selected(fk_leg+ik_leg):
+ layout.prop(pose_bones[ik_leg[0]], '["ikfk_switch"]', text="FK / IK (" + ik_leg[0] + ")", slider=True)
+if is_selected(fk_leg):
+ layout.prop(pose_bones[fk_leg[0]], '["isolate"]', text="Isolate Rotation (" + fk_leg[0] + ")", slider=True)
+"""
+
+
+class Rig:
+ """ A leg rig, with IK/FK switching, a hinge switch, and foot roll.
+
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ Store any data or references to data that will be needed later on.
+ In particular, store names of bones that will be needed.
+ Do NOT change any data in the scene. This is a gathering phase only.
+
+ """
+ # Gather deform rig
+ self.deform_rig = deform.Rig(obj, bone, params)
+
+ # Gather FK rig
+ self.fk_rig = fk.Rig(obj, bone, params)
+
+ # Gather IK rig
+ self.ik_rig = ik.Rig(obj, bone, params, ikfk_switch=True)
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ self.deform_rig.generate()
+ fk_controls = self.fk_rig.generate()
+ ik_controls = self.ik_rig.generate()
+ return [script % (fk_controls[0], fk_controls[1], fk_controls[2], ik_controls[0], ik_controls[1], ik_controls[2])]
+
+ @classmethod
+ def add_parameters(self, group):
+ """ Add the parameters of this rig type to the
+ RigifyParameters IDPropertyGroup
+
+ """
+ items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')]
+ group.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X')
+
+ group.separate_ik_layers = bpy.props.BoolProperty(name="Separate IK Control Layers:", default=False, description="Enable putting the ik controls on a separate layer from the fk controls.")
+ group.ik_layers = bpy.props.BoolVectorProperty(size=32, description="Layers for the ik controls to be on.")
+
+ group.use_thigh_twist = bpy.props.BoolProperty(name="Thigh Twist", default=True, description="Generate the dual-bone twist setup for the thigh.")
+ group.use_shin_twist = bpy.props.BoolProperty(name="Shin Twist", default=True, description="Generate the dual-bone twist setup for the shin.")
+
+ @classmethod
+ def parameters_ui(self, layout, obj, bone):
+ """ Create the ui for the rig parameters.
+
+ """
+ params = obj.pose.bones[bone].rigify_parameters[0]
+
+ r = layout.row()
+ r.prop(params, "separate_ik_layers")
+
+ r = layout.row()
+ r.active = params.separate_ik_layers
+
+ col = r.column(align=True)
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=0, toggle=True, text="")
+ row.prop(params, "ik_layers", index=1, toggle=True, text="")
+ row.prop(params, "ik_layers", index=2, toggle=True, text="")
+ row.prop(params, "ik_layers", index=3, toggle=True, text="")
+ row.prop(params, "ik_layers", index=4, toggle=True, text="")
+ row.prop(params, "ik_layers", index=5, toggle=True, text="")
+ row.prop(params, "ik_layers", index=6, toggle=True, text="")
+ row.prop(params, "ik_layers", index=7, toggle=True, text="")
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=16, toggle=True, text="")
+ row.prop(params, "ik_layers", index=17, toggle=True, text="")
+ row.prop(params, "ik_layers", index=18, toggle=True, text="")
+ row.prop(params, "ik_layers", index=19, toggle=True, text="")
+ row.prop(params, "ik_layers", index=20, toggle=True, text="")
+ row.prop(params, "ik_layers", index=21, toggle=True, text="")
+ row.prop(params, "ik_layers", index=22, toggle=True, text="")
+ row.prop(params, "ik_layers", index=23, toggle=True, text="")
+
+ col = r.column(align=True)
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=8, toggle=True, text="")
+ row.prop(params, "ik_layers", index=9, toggle=True, text="")
+ row.prop(params, "ik_layers", index=10, toggle=True, text="")
+ row.prop(params, "ik_layers", index=11, toggle=True, text="")
+ row.prop(params, "ik_layers", index=12, toggle=True, text="")
+ row.prop(params, "ik_layers", index=13, toggle=True, text="")
+ row.prop(params, "ik_layers", index=14, toggle=True, text="")
+ row.prop(params, "ik_layers", index=15, toggle=True, text="")
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=24, toggle=True, text="")
+ row.prop(params, "ik_layers", index=25, toggle=True, text="")
+ row.prop(params, "ik_layers", index=26, toggle=True, text="")
+ row.prop(params, "ik_layers", index=27, toggle=True, text="")
+ row.prop(params, "ik_layers", index=28, toggle=True, text="")
+ row.prop(params, "ik_layers", index=29, toggle=True, text="")
+ row.prop(params, "ik_layers", index=30, toggle=True, text="")
+ row.prop(params, "ik_layers", index=31, toggle=True, text="")
+
+ r = layout.row()
+ r.label(text="Knee rotation axis:")
+ r.prop(params, "primary_rotation_axis", text="")
+
+ col = layout.column()
+ col.prop(params, "use_thigh_twist")
+ col.prop(params, "use_shin_twist")
+
+ @classmethod
+ def create_sample(self, obj):
+ # generated by rigify.utils.write_meta_rig
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bones = {}
+
+ bone = arm.edit_bones.new('thigh')
+ bone.head[:] = -0.0000, 0.0000, 1.0000
+ bone.tail[:] = -0.0000, -0.0500, 0.5000
+ bone.roll = -0.0000
+ bone.use_connect = False
+ bones['thigh'] = bone.name
+ bone = arm.edit_bones.new('shin')
+ bone.head[:] = -0.0000, -0.0500, 0.5000
+ bone.tail[:] = -0.0000, 0.0000, 0.1000
+ bone.roll = -0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['thigh']]
+ bones['shin'] = bone.name
+ bone = arm.edit_bones.new('foot')
+ bone.head[:] = -0.0000, 0.0000, 0.1000
+ bone.tail[:] = 0.0000, -0.1200, 0.0300
+ bone.roll = 0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['shin']]
+ bones['foot'] = bone.name
+ bone = arm.edit_bones.new('heel')
+ bone.head[:] = -0.0000, 0.0000, 0.1000
+ bone.tail[:] = -0.0000, 0.0600, 0.0000
+ bone.roll = -0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['shin']]
+ bones['heel'] = bone.name
+ bone = arm.edit_bones.new('toe')
+ bone.head[:] = 0.0000, -0.1200, 0.0300
+ bone.tail[:] = 0.0000, -0.2000, 0.0300
+ bone.roll = 3.1416
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['foot']]
+ bones['toe'] = bone.name
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bones['thigh']]
+ pbone.rigify_type = 'biped.leg'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.rigify_parameters.add()
+ pbone = obj.pose.bones[bones['shin']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone = obj.pose.bones[bones['foot']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone = obj.pose.bones[bones['heel']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone = obj.pose.bones[bones['toe']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in arm.edit_bones:
+ bone.select = False
+ bone.select_head = False
+ bone.select_tail = False
+ for b in bones:
+ bone = arm.edit_bones[bones[b]]
+ bone.select = True
+ bone.select_head = True
+ bone.select_tail = True
+ arm.edit_bones.active = bone
+
diff --git a/rigify/rigs/biped/leg/deform.py b/rigify/rigs/biped/leg/deform.py
new file mode 100644
index 00000000..df6c6a60
--- /dev/null
+++ b/rigify/rigs/biped/leg/deform.py
@@ -0,0 +1,264 @@
+#====================== 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 math import acos, degrees
+from mathutils import Vector, Matrix
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone, flip_bone, put_bone
+from rigify.utils import connected_children_names
+from rigify.utils import strip_org, make_mechanism_name, make_deformer_name
+
+
+def align_roll(obj, bone1, bone2):
+ bone1_e = obj.data.edit_bones[bone1]
+ bone2_e = obj.data.edit_bones[bone2]
+
+ bone1_e.roll = 0.0
+
+ # Get the directions the bones are pointing in, as vectors
+ y1 = bone1_e.y_axis
+ x1 = bone1_e.x_axis
+ y2 = bone2_e.y_axis
+ x2 = bone2_e.x_axis
+
+ # Get the shortest axis to rotate bone1 on to point in the same direction as bone2
+ axis = y1.cross(y2)
+ axis.normalize()
+
+ # Angle to rotate on that shortest axis
+ angle = y1.angle(y2)
+
+ # Create rotation matrix to make bone1 point in the same direction as bone2
+ rot_mat = Matrix.Rotation(angle, 3, axis)
+
+ # Roll factor
+ x3 = x1 * rot_mat
+ dot = x2 * x3
+ if dot > 1.0:
+ dot = 1.0
+ elif dot < -1.0:
+ dot = -1.0
+ roll = acos(dot)
+
+ # Set the roll
+ bone1_e.roll = roll
+
+ # Check if we rolled in the right direction
+ x3 = bone1_e.x_axis * rot_mat
+ check = x2 * x3
+
+ # If not, reverse
+ if check < 0.9999:
+ bone1_e.roll = -roll
+
+
+class Rig:
+ """ A leg deform-bone setup.
+
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ Store any data or references to data that will be needed later on.
+ In particular, store references to bones that will be needed, and
+ store names of bones that will be needed.
+ Do NOT change any data in the scene. This is a gathering phase only.
+
+ """
+ self.obj = obj
+ self.params = params
+
+ # Get the chain of 2 connected bones
+ leg_bones = [bone] + connected_children_names(self.obj, bone)[:2]
+
+ if len(leg_bones) != 2:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone)))
+
+ # Get the foot and heel
+ foot = None
+ heel = None
+ for b in self.obj.data.bones[leg_bones[1]].children:
+ if b.use_connect == True:
+ if len(b.children) == 0:
+ heel = b.name
+ else:
+ foot = b.name
+
+ if foot == None or heel == None:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone)))
+
+ # Get the toe
+ toe = None
+ for b in self.obj.data.bones[foot].children:
+ if b.use_connect == True:
+ toe = b.name
+
+ if toe == None:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone)))
+
+ self.org_bones = leg_bones + [foot, toe, heel]
+
+ # Get rig parameters
+ self.use_thigh_twist = params.use_thigh_twist
+ self.use_shin_twist = params.use_shin_twist
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Create upper arm bones
+ if self.use_thigh_twist:
+ thigh1 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".01")))
+ thigh2 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".02")))
+ utip = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[0] + ".tip")))
+ else:
+ thigh = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0])))
+
+ # Create forearm bones
+ if self.use_shin_twist:
+ shin1 = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1] + ".01")))
+ shin2 = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1] + ".02")))
+ stip = copy_bone(self.obj, self.org_bones[1], make_mechanism_name(strip_org(self.org_bones[1] + ".tip")))
+ else:
+ shin = copy_bone(self.obj, self.org_bones[1], make_deformer_name(strip_org(self.org_bones[1])))
+
+ # Create foot bone
+ foot = copy_bone(self.obj, self.org_bones[2], make_deformer_name(strip_org(self.org_bones[2])))
+
+ # Create toe bone
+ toe = copy_bone(self.obj, self.org_bones[3], make_deformer_name(strip_org(self.org_bones[3])))
+
+ # Get edit bones
+ eb = self.obj.data.edit_bones
+
+ org_thigh_e = eb[self.org_bones[0]]
+ if self.use_thigh_twist:
+ thigh1_e = eb[thigh1]
+ thigh2_e = eb[thigh2]
+ utip_e = eb[utip]
+ else:
+ thigh_e = eb[thigh]
+
+ org_shin_e = eb[self.org_bones[1]]
+ if self.use_shin_twist:
+ shin1_e = eb[shin1]
+ shin2_e = eb[shin2]
+ stip_e = eb[stip]
+ else:
+ shin_e = eb[shin]
+
+ org_foot_e = eb[self.org_bones[2]]
+ foot_e = eb[foot]
+
+ org_toe_e = eb[self.org_bones[3]]
+ toe_e = eb[toe]
+
+ # Parent and position thigh bones
+ if self.use_thigh_twist:
+ thigh1_e.use_connect = False
+ thigh2_e.use_connect = False
+ utip_e.use_connect = False
+
+ thigh1_e.parent = org_thigh_e.parent
+ thigh2_e.parent = org_thigh_e
+ utip_e.parent = org_thigh_e
+
+ center = Vector((org_thigh_e.head + org_thigh_e.tail) / 2)
+
+ thigh1_e.tail = center
+ thigh2_e.head = center
+ put_bone(self.obj, utip, org_thigh_e.tail)
+ utip_e.length = org_thigh_e.length / 8
+ else:
+ thigh_e.use_connect = False
+ thigh_e.parent = org_thigh_e
+
+ # Parent and position shin bones
+ if self.use_shin_twist:
+ shin1_e.use_connect = False
+ shin2_e.use_connect = False
+ stip_e.use_connect = False
+
+ shin1_e.parent = org_shin_e
+ shin2_e.parent = org_shin_e
+ stip_e.parent = org_shin_e
+
+ center = Vector((org_shin_e.head + org_shin_e.tail) / 2)
+
+ shin1_e.tail = center
+ shin2_e.head = center
+ put_bone(self.obj, stip, org_shin_e.tail)
+ stip_e.length = org_shin_e.length / 8
+
+ # Align roll of shin2 with foot
+ align_roll(self.obj, shin2, foot)
+ else:
+ shin_e.use_connect = False
+ shin_e.parent = org_shin_e
+
+ # Parent foot
+ foot_e.use_connect = False
+ foot_e.parent = org_foot_e
+
+ # Parent toe
+ toe_e.use_connect = False
+ toe_e.parent = org_toe_e
+
+ # Object mode, get pose bones
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ if self.use_thigh_twist:
+ thigh1_p = pb[thigh1]
+ if self.use_shin_twist:
+ shin2_p = pb[shin2]
+ foot_p = pb[foot]
+
+ # Thigh constraints
+ if self.use_thigh_twist:
+ con = thigh1_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = self.org_bones[0]
+
+ con = thigh1_p.constraints.new('COPY_SCALE')
+ con.name = "copy_scale"
+ con.target = self.obj
+ con.subtarget = self.org_bones[0]
+
+ con = thigh1_p.constraints.new('DAMPED_TRACK')
+ con.name = "track_to"
+ con.target = self.obj
+ con.subtarget = utip
+
+ # Shin constraints
+ if self.use_shin_twist:
+ con = shin2_p.constraints.new('COPY_ROTATION')
+ con.name = "copy_rotation"
+ con.target = self.obj
+ con.subtarget = foot
+
+ con = shin2_p.constraints.new('DAMPED_TRACK')
+ con.name = "track_to"
+ con.target = self.obj
+ con.subtarget = stip
+
diff --git a/rigify/rigs/biped/leg/fk.py b/rigify/rigs/biped/leg/fk.py
new file mode 100644
index 00000000..a212d445
--- /dev/null
+++ b/rigify/rigs/biped/leg/fk.py
@@ -0,0 +1,246 @@
+#====================== 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
+import math
+from mathutils import Vector
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone, flip_bone, put_bone
+from rigify.utils import connected_children_names
+from rigify.utils import strip_org, make_mechanism_name, make_deformer_name
+from rigify.utils import get_layers
+from rigify.utils import create_widget, create_limb_widget
+from rna_prop_ui import rna_idprop_ui_prop_get
+
+
+class Rig:
+ """ An FK leg rig, with hinge switch.
+
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ Store any data or references to data that will be needed later on.
+ In particular, store references to bones that will be needed, and
+ store names of bones that will be needed.
+ Do NOT change any data in the scene. This is a gathering phase only.
+
+ """
+ self.obj = obj
+ self.params = params
+
+ # Get the chain of 2 connected bones
+ leg_bones = [bone] + connected_children_names(self.obj, bone)[:2]
+
+ if len(leg_bones) != 2:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone)))
+
+ # Get the foot and heel
+ foot = None
+ heel = None
+ for b in self.obj.data.bones[leg_bones[1]].children:
+ if b.use_connect == True:
+ if len(b.children) == 0:
+ heel = b.name
+ else:
+ foot = b.name
+
+ if foot == None or heel == None:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone)))
+
+ # Get the toe
+ toe = None
+ for b in self.obj.data.bones[foot].children:
+ if b.use_connect == True:
+ toe = b.name
+
+ # Get the toe
+ if toe == None:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone)))
+
+ self.org_bones = leg_bones + [foot, toe, heel]
+
+ # Get (optional) parent
+ if self.obj.data.bones[bone].parent == None:
+ self.org_parent = None
+ else:
+ self.org_parent = self.obj.data.bones[bone].parent.name
+
+ # Get rig parameters
+ if "layers" in params:
+ self.layers = get_layers(params["layers"])
+ else:
+ self.layers = None
+
+ self.primary_rotation_axis = params.primary_rotation_axis
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Create the control bones
+ thigh = copy_bone(self.obj, self.org_bones[0], strip_org(self.org_bones[0]))
+ shin = copy_bone(self.obj, self.org_bones[1], strip_org(self.org_bones[1]))
+ foot = copy_bone(self.obj, self.org_bones[2], strip_org(self.org_bones[2]))
+
+ # Create the foot mechanism bone
+ foot_mch = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(self.org_bones[2])))
+
+ # Create the hinge bones
+ if self.org_parent != None:
+ hinge = copy_bone(self.obj, self.org_parent, make_mechanism_name(thigh + ".hinge"))
+ socket1 = copy_bone(self.obj, thigh, make_mechanism_name(thigh + ".socket1"))
+ socket2 = copy_bone(self.obj, thigh, make_mechanism_name(thigh + ".socket2"))
+
+ # Get edit bones
+ eb = self.obj.data.edit_bones
+
+ thigh_e = eb[thigh]
+ shin_e = eb[shin]
+ foot_e = eb[foot]
+ foot_mch_e = eb[foot_mch]
+
+ if self.org_parent != None:
+ hinge_e = eb[hinge]
+ socket1_e = eb[socket1]
+ socket2_e = eb[socket2]
+
+ # Parenting
+ shin_e.parent = thigh_e
+ foot_e.parent = shin_e
+
+ foot_mch_e.use_connect = False
+ foot_mch_e.parent = foot_e
+
+ if self.org_parent != None:
+ hinge_e.use_connect = False
+ socket1_e.use_connect = False
+ socket2_e.use_connect = False
+
+ thigh_e.parent = hinge_e
+ hinge_e.parent = socket2_e
+ socket2_e.parent = None
+
+ # Positioning
+ vec = Vector(eb[self.org_bones[3]].vector)
+ vec = vec.normalize()
+ foot_e.tail = foot_e.head + (vec * foot_e.length)
+ foot_e.roll = eb[self.org_bones[3]].roll
+
+ if self.org_parent != None:
+ center = (hinge_e.head + hinge_e.tail) / 2
+ hinge_e.head = center
+ socket1_e.length /= 4
+ socket2_e.length /= 3
+
+ # Object mode, get pose bones
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ thigh_p = pb[thigh]
+ shin_p = pb[shin]
+ foot_p = pb[foot]
+
+ if self.org_parent != None:
+ socket1_p = pb[socket1]
+ socket2_p = pb[socket2]
+
+ # Set the elbow to only bend on the x-axis.
+ shin_p.rotation_mode = 'XYZ'
+ if 'X' in self.primary_rotation_axis:
+ shin_p.lock_rotation = (False, True, True)
+ elif 'Y' in self.primary_rotation_axis:
+ shin_p.lock_rotation = (True, False, True)
+ else:
+ shin_p.lock_rotation = (True, True, False)
+
+ # Set up custom properties
+ if self.org_parent != None:
+ prop = rna_idprop_ui_prop_get(thigh_p, "isolate", create=True)
+ thigh_p["isolate"] = 0.0
+ prop["soft_min"] = prop["min"] = 0.0
+ prop["soft_max"] = prop["max"] = 1.0
+
+ # Hinge constraints / drivers
+ if self.org_parent != None:
+ con = socket2_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = socket1
+
+ con = socket2_p.constraints.new('COPY_TRANSFORMS')
+ con.name = "isolate_off"
+ con.target = self.obj
+ con.subtarget = socket1
+
+ # Driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = thigh_p.path_from_id() + '["isolate"]'
+ mod = fcurve.modifiers[0]
+ mod.poly_order = 1
+ mod.coefficients[0] = 1.0
+ mod.coefficients[1] = -1.0
+
+ # Constrain org bones to controls
+ con = pb[self.org_bones[0]].constraints.new('COPY_TRANSFORMS')
+ con.name = "fk"
+ con.target = self.obj
+ con.subtarget = thigh
+
+ con = pb[self.org_bones[1]].constraints.new('COPY_TRANSFORMS')
+ con.name = "fk"
+ con.target = self.obj
+ con.subtarget = shin
+
+ con = pb[self.org_bones[2]].constraints.new('COPY_TRANSFORMS')
+ con.name = "fk"
+ con.target = self.obj
+ con.subtarget = foot_mch
+
+ # Set layers if specified
+ if self.layers:
+ thigh_p.bone.layers = self.layers
+ shin_p.bone.layers = self.layers
+ foot_p.bone.layers = self.layers
+
+ # Create control widgets
+ create_limb_widget(self.obj, thigh)
+ create_limb_widget(self.obj, shin)
+
+ ob = create_widget(self.obj, foot)
+ if ob != None:
+ verts = [(0.7, 1.5, 0.0), (0.7, -0.25, 0.0), (-0.7, -0.25, 0.0), (-0.7, 1.5, 0.0), (0.7, 0.723, 0.0), (-0.7, 0.723, 0.0), (0.7, 0.0, 0.0), (-0.7, 0.0, 0.0)]
+ edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)]
+ mesh = ob.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+ mod = ob.modifiers.new("subsurf", 'SUBSURF')
+ mod.levels = 2
+
+ return [thigh, shin, foot]
+
diff --git a/rigify/rigs/biped/leg/ik.py b/rigify/rigs/biped/leg/ik.py
new file mode 100644
index 00000000..a04b57dc
--- /dev/null
+++ b/rigify/rigs/biped/leg/ik.py
@@ -0,0 +1,520 @@
+#====================== 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 Vector
+from math import pi, acos
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone, flip_bone, put_bone
+from rigify.utils import connected_children_names
+from rigify.utils import strip_org, make_mechanism_name, insert_before_lr
+from rigify.utils import get_layers
+from rigify.utils import create_widget, create_line_widget, create_sphere_widget, create_circle_widget
+from rna_prop_ui import rna_idprop_ui_prop_get
+
+
+def align_x_axis(obj, bone, vec):
+ """ Aligns the x-axis of a bone to the given vector. This only works if it
+ can be an exact match.
+ Must be in edit mode.
+
+ """
+ vec.normalize()
+ bone_e = obj.data.edit_bones[bone]
+ dot = max(-1.0, min(1.0, bone_e.x_axis.dot(vec)))
+ angle = acos(dot)
+
+ bone_e.roll += angle
+
+ dot1 = bone_e.x_axis.dot(vec)
+
+ bone_e.roll -= angle * 2
+
+ dot2 = bone_e.x_axis.dot(vec)
+
+ if dot1 > dot2:
+ bone_e.roll += angle * 2
+
+
+def angle_on_plane(plane, vec1, vec2):
+ """ Return the angle between two vectors projected onto a plane.
+ """
+ plane.normalize()
+ vec1 = vec1 - (plane * (vec1.dot(plane)))
+ vec2 = vec2 - (plane * (vec2.dot(plane)))
+ vec1.normalize()
+ vec2.normalize()
+
+ # Determine the angle
+ angle = acos(max(-1.0, min(1.0, vec1.dot(vec2))))
+
+ if angle < 0.00001: # close enough to zero that sign doesn't matter
+ return angle
+
+ # Determine the sign of the angle
+ vec3 = vec2.cross(vec1)
+ vec3.normalize()
+ sign = vec3.dot(plane)
+ if sign >= 0:
+ sign = 1
+ else:
+ sign = -1
+
+ return angle * sign
+
+
+class Rig:
+ """ An IK leg rig, with an optional ik/fk switch.
+
+ """
+ def __init__(self, obj, bone, params, ikfk_switch=False):
+ """ Gather and validate data about the rig.
+ Store any data or references to data that will be needed later on.
+ In particular, store references to bones that will be needed, and
+ store names of bones that will be needed.
+ Do NOT change any data in the scene. This is a gathering phase only.
+ """
+ self.obj = obj
+ self.params = params
+ self.switch = ikfk_switch
+
+ # Get the chain of 2 connected bones
+ leg_bones = [bone] + connected_children_names(self.obj, bone)[:2]
+
+ if len(leg_bones) != 2:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone)))
+
+ # Get the foot and heel
+ foot = None
+ heel = None
+ for b in self.obj.data.bones[leg_bones[1]].children:
+ if b.use_connect == True:
+ if len(b.children) == 0:
+ heel = b.name
+ else:
+ foot = b.name
+
+ if foot == None or heel == None:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone)))
+
+ # Get the toe
+ toe = None
+ for b in self.obj.data.bones[foot].children:
+ if b.use_connect == True:
+ toe = b.name
+
+ # Get toe
+ if toe == None:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': incorrect bone configuration for rig type." % (strip_org(bone)))
+
+ self.org_bones = leg_bones + [foot, toe, heel]
+
+ # Get rig parameters
+ if params.separate_ik_layers:
+ self.layers = list(params.ik_layers)
+ else:
+ self.layers = None
+
+ self.primary_rotation_axis = params.primary_rotation_axis
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Create the bones
+ thigh = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[0], "_ik"))))
+ shin = copy_bone(self.obj, self.org_bones[1], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[1], "_ik"))))
+
+ foot = copy_bone(self.obj, self.org_bones[2], strip_org(insert_before_lr(self.org_bones[2], "_ik")))
+ foot_ik_target = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(insert_before_lr(self.org_bones[2], "_ik_target"))))
+ pole = copy_bone(self.obj, self.org_bones[0], strip_org(insert_before_lr(self.org_bones[0], "_pole")))
+
+ toe = copy_bone(self.obj, self.org_bones[3], strip_org(self.org_bones[3]))
+ toe_parent = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(self.org_bones[3] + ".parent")))
+ toe_parent_socket1 = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(self.org_bones[3] + ".socket1")))
+ toe_parent_socket2 = copy_bone(self.obj, self.org_bones[2], make_mechanism_name(strip_org(self.org_bones[3] + ".socket2")))
+
+ foot_roll = copy_bone(self.obj, self.org_bones[4], strip_org(insert_before_lr(self.org_bones[2], "_roll")))
+ roll1 = copy_bone(self.obj, self.org_bones[4], make_mechanism_name(strip_org(self.org_bones[2] + ".roll")))
+ roll2 = copy_bone(self.obj, self.org_bones[4], make_mechanism_name(strip_org(self.org_bones[2] + ".roll")))
+
+ visfoot = copy_bone(self.obj, self.org_bones[2], "VIS-" + strip_org(insert_before_lr(self.org_bones[2], "_ik")))
+ vispole = copy_bone(self.obj, self.org_bones[1], "VIS-" + strip_org(insert_before_lr(self.org_bones[0], "_pole")))
+
+ # Get edit bones
+ eb = self.obj.data.edit_bones
+
+ org_foot_e = eb[self.org_bones[2]]
+ thigh_e = eb[thigh]
+ shin_e = eb[shin]
+ foot_e = eb[foot]
+ foot_ik_target_e = eb[foot_ik_target]
+ pole_e = eb[pole]
+ toe_e = eb[toe]
+ toe_parent_e = eb[toe_parent]
+ toe_parent_socket1_e = eb[toe_parent_socket1]
+ toe_parent_socket2_e = eb[toe_parent_socket2]
+ foot_roll_e = eb[foot_roll]
+ roll1_e = eb[roll1]
+ roll2_e = eb[roll2]
+ visfoot_e = eb[visfoot]
+ vispole_e = eb[vispole]
+
+ # Parenting
+ shin_e.parent = thigh_e
+
+ foot_e.use_connect = False
+ foot_e.parent = None
+ foot_ik_target_e.use_connect = False
+ foot_ik_target_e.parent = roll2_e
+
+ pole_e.use_connect = False
+ pole_e.parent = foot_e
+
+ toe_e.parent = toe_parent_e
+ toe_parent_e.use_connect = False
+ toe_parent_e.parent = toe_parent_socket1_e
+ toe_parent_socket1_e.use_connect = False
+ toe_parent_socket1_e.parent = roll1_e
+ toe_parent_socket2_e.use_connect = False
+ toe_parent_socket2_e.parent = eb[self.org_bones[2]]
+
+ foot_roll_e.use_connect = False
+ foot_roll_e.parent = foot_e
+
+ roll1_e.use_connect = False
+ roll1_e.parent = foot_e
+
+ roll2_e.use_connect = False
+ roll2_e.parent = roll1_e
+
+ visfoot_e.use_connect = False
+ visfoot_e.parent = None
+
+ vispole_e.use_connect = False
+ vispole_e.parent = None
+
+ # Misc
+ foot_e.use_local_location = False
+
+ visfoot_e.hide_select = True
+ vispole_e.hide_select = True
+
+ # Positioning
+ vec = Vector(toe_e.vector)
+ vec = vec.normalize()
+ foot_e.tail = foot_e.head + (vec * foot_e.length)
+ foot_e.roll = toe_e.roll
+
+ v1 = shin_e.tail - thigh_e.head
+
+ if 'X' in self.primary_rotation_axis or 'Y' in self.primary_rotation_axis:
+ v2 = v1.cross(shin_e.x_axis)
+ if (v2 * shin_e.z_axis) > 0.0:
+ v2 *= -1.0
+ else:
+ v2 = v1.cross(shin_e.z_axis)
+ if (v2 * shin_e.x_axis) < 0.0:
+ v2 *= -1.0
+ v2.normalize()
+ v2 *= v1.length
+
+ if '-' in self.primary_rotation_axis:
+ v2 *= -1
+
+ pole_e.head = shin_e.head + v2
+ pole_e.tail = pole_e.head + (Vector((0, 1, 0)) * (v1.length / 8))
+ pole_e.roll = 0.0
+
+ flip_bone(self.obj, toe_parent_socket1)
+ flip_bone(self.obj, toe_parent_socket2)
+ toe_parent_socket1_e.head = Vector(org_foot_e.tail)
+ toe_parent_socket2_e.head = Vector(org_foot_e.tail)
+ toe_parent_socket1_e.tail = Vector(org_foot_e.tail) + (Vector((0, 0, 1)) * foot_e.length / 2)
+ toe_parent_socket2_e.tail = Vector(org_foot_e.tail) + (Vector((0, 0, 1)) * foot_e.length / 3)
+ toe_parent_socket2_e.roll = toe_parent_socket1_e.roll
+
+ tail = Vector(roll1_e.tail)
+ roll1_e.tail = Vector(org_foot_e.tail)
+ roll1_e.tail = Vector(org_foot_e.tail)
+ roll1_e.head = tail
+ roll2_e.head = Vector(org_foot_e.tail)
+ foot_roll_e.head = Vector(org_foot_e.tail)
+ put_bone(self.obj, foot_roll, roll1_e.head)
+ foot_roll_e.length /= 2
+
+ roll_axis = roll1_e.vector.cross(org_foot_e.vector)
+ align_x_axis(self.obj, roll1, roll_axis)
+ align_x_axis(self.obj, roll2, roll_axis)
+ foot_roll_e.roll = roll2_e.roll
+
+ visfoot_e.tail = visfoot_e.head + Vector((0, 0, v1.length / 32))
+ vispole_e.tail = vispole_e.head + Vector((0, 0, v1.length / 32))
+
+ # Weird alignment issues. Fix.
+ toe_parent_e.head = Vector(org_foot_e.head)
+ toe_parent_e.tail = Vector(org_foot_e.tail)
+ toe_parent_e.roll = org_foot_e.roll
+
+ foot_e.head = Vector(org_foot_e.head)
+
+ foot_ik_target_e.head = Vector(org_foot_e.head)
+ foot_ik_target_e.tail = Vector(org_foot_e.tail)
+
+ # Determine the pole offset value
+ plane = (shin_e.tail - thigh_e.head).normalize()
+ vec1 = thigh_e.x_axis.normalize()
+ vec2 = (pole_e.head - thigh_e.head).normalize()
+ pole_offset = angle_on_plane(plane, vec1, vec2)
+
+ # Object mode, get pose bones
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ thigh_p = pb[thigh]
+ shin_p = pb[shin]
+ foot_p = pb[foot]
+ pole_p = pb[pole]
+ foot_roll_p = pb[foot_roll]
+ roll1_p = pb[roll1]
+ roll2_p = pb[roll2]
+ toe_p = pb[toe]
+ toe_parent_p = pb[toe_parent]
+ toe_parent_socket1_p = pb[toe_parent_socket1]
+ visfoot_p = pb[visfoot]
+ vispole_p = pb[vispole]
+
+ # Set the knee to only bend on the primary axis.
+ if 'X' in self.primary_rotation_axis:
+ shin_p.lock_ik_y = True
+ shin_p.lock_ik_z = True
+ elif 'Y' in self.primary_rotation_axis:
+ shin_p.lock_ik_x = True
+ shin_p.lock_ik_z = True
+ else:
+ shin_p.lock_ik_x = True
+ shin_p.lock_ik_y = True
+
+ # Foot roll control only rotates on x-axis.
+ foot_roll_p.rotation_mode = 'XYZ'
+ foot_roll_p.lock_rotation = False, True, True
+ foot_roll_p.lock_location = True, True, True
+ foot_roll_p.lock_scale = True, True, True
+
+ # Pole target only translates
+ pole_p.lock_location = False, False, False
+ pole_p.lock_rotation = True, True, True
+ pole_p.lock_rotation_w = True
+ pole_p.lock_scale = True, True, True
+
+ # Set up custom properties
+ if self.switch == True:
+ prop = rna_idprop_ui_prop_get(foot_p, "ikfk_switch", create=True)
+ foot_p["ikfk_switch"] = 0.0
+ prop["soft_min"] = prop["min"] = 0.0
+ prop["soft_max"] = prop["max"] = 1.0
+
+ # IK Constraint
+ con = shin_p.constraints.new('IK')
+ con.name = "ik"
+ con.target = self.obj
+ con.subtarget = foot_ik_target
+ con.pole_target = self.obj
+ con.pole_subtarget = pole
+ con.pole_angle = pole_offset
+ con.chain_count = 2
+
+ # toe_parent constraint
+ con = toe_parent_socket1_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = toe_parent_socket2
+
+ con = toe_parent_socket1_p.constraints.new('COPY_SCALE')
+ con.name = "copy_scale"
+ con.target = self.obj
+ con.subtarget = toe_parent_socket2
+
+ con = toe_parent_socket1_p.constraints.new('COPY_TRANSFORMS') # drive with IK switch
+ con.name = "fk"
+ con.target = self.obj
+ con.subtarget = toe_parent_socket2
+
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = foot_p.path_from_id() + '["ikfk_switch"]'
+ mod = fcurve.modifiers[0]
+ mod.poly_order = 1
+ mod.coefficients[0] = 1.0
+ mod.coefficients[1] = -1.0
+
+ # Foot roll constraints
+ con = roll1_p.constraints.new('COPY_ROTATION')
+ con.name = "roll"
+ con.target = self.obj
+ con.subtarget = foot_roll
+ con.target_space = 'LOCAL'
+ con.owner_space = 'LOCAL'
+
+ con = roll1_p.constraints.new('LIMIT_ROTATION')
+ con.name = "limit_roll"
+ con.use_limit_x = True
+ con.min_x = -180
+ con.max_x = 0
+ con.owner_space = 'LOCAL'
+
+ con = roll2_p.constraints.new('COPY_ROTATION')
+ con.name = "roll"
+ con.target = self.obj
+ con.subtarget = foot_roll
+ con.target_space = 'LOCAL'
+ con.owner_space = 'LOCAL'
+
+ con = roll2_p.constraints.new('LIMIT_ROTATION')
+ con.name = "limit_roll"
+ con.use_limit_x = True
+ con.min_x = 0
+ con.max_x = 180
+ con.owner_space = 'LOCAL'
+
+ # Constrain org bones to controls
+ con = pb[self.org_bones[0]].constraints.new('COPY_TRANSFORMS')
+ con.name = "ik"
+ con.target = self.obj
+ con.subtarget = thigh
+ if self.switch == True:
+ # IK/FK switch driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = foot_p.path_from_id() + '["ikfk_switch"]'
+
+ con = pb[self.org_bones[1]].constraints.new('COPY_TRANSFORMS')
+ con.name = "ik"
+ con.target = self.obj
+ con.subtarget = shin
+ if self.switch == True:
+ # IK/FK switch driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = foot_p.path_from_id() + '["ikfk_switch"]'
+
+ con = pb[self.org_bones[2]].constraints.new('COPY_TRANSFORMS')
+ con.name = "ik"
+ con.target = self.obj
+ con.subtarget = foot_ik_target
+ if self.switch == True:
+ # IK/FK switch driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = foot_p.path_from_id() + '["ikfk_switch"]'
+
+ con = pb[self.org_bones[3]].constraints.new('COPY_TRANSFORMS')
+ con.name = "copy_transforms"
+ con.target = self.obj
+ con.subtarget = toe
+
+ # VIS foot constraints
+ con = visfoot_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_loc"
+ con.target = self.obj
+ con.subtarget = self.org_bones[2]
+
+ con = visfoot_p.constraints.new('STRETCH_TO')
+ con.name = "stretch_to"
+ con.target = self.obj
+ con.subtarget = foot
+ con.volume = 'NO_VOLUME'
+ con.rest_length = visfoot_p.length
+
+ # VIS pole constraints
+ con = vispole_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_loc"
+ con.target = self.obj
+ con.subtarget = self.org_bones[1]
+
+ con = vispole_p.constraints.new('STRETCH_TO')
+ con.name = "stretch_to"
+ con.target = self.obj
+ con.subtarget = pole
+ con.volume = 'NO_VOLUME'
+ con.rest_length = vispole_p.length
+
+ # Set layers if specified
+ if self.layers:
+ foot_p.bone.layers = self.layers
+ pole_p.bone.layers = self.layers
+ foot_roll_p.bone.layers = self.layers
+ visfoot_p.bone.layers = self.layers
+ vispole_p.bone.layers = self.layers
+
+ toe_p.bone.layers = [(i[0] or i[1]) for i in zip(toe_p.bone.layers, self.layers)] # Both FK and IK layers
+
+ # Create widgets
+ create_line_widget(self.obj, vispole)
+ create_line_widget(self.obj, visfoot)
+ create_sphere_widget(self.obj, pole)
+ create_circle_widget(self.obj, toe, radius=0.7, head_tail=0.5)
+
+ ob = create_widget(self.obj, foot)
+ if ob != None:
+ verts = [(0.7, 1.5, 0.0), (0.7, -0.25, 0.0), (-0.7, -0.25, 0.0), (-0.7, 1.5, 0.0), (0.7, 0.723, 0.0), (-0.7, 0.723, 0.0), (0.7, 0.0, 0.0), (-0.7, 0.0, 0.0)]
+ edges = [(1, 2), (0, 3), (0, 4), (3, 5), (4, 6), (1, 6), (5, 7), (2, 7)]
+ mesh = ob.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+ mod = ob.modifiers.new("subsurf", 'SUBSURF')
+ mod.levels = 2
+
+ ob = create_widget(self.obj, foot_roll)
+ if ob != None:
+ verts = [(0.3999999761581421, 0.766044557094574, 0.6427875757217407), (0.17668449878692627, 3.823702598992895e-08, 3.2084670920085046e-08), (-0.17668461799621582, 9.874240447516058e-08, 8.285470443070153e-08), (-0.39999961853027344, 0.7660449147224426, 0.6427879333496094), (0.3562471270561218, 0.6159579753875732, 0.5168500542640686), (-0.35624682903289795, 0.6159582138061523, 0.5168502926826477), (0.20492683351039886, 0.09688037633895874, 0.0812922865152359), (-0.20492687821388245, 0.0968804731965065, 0.08129236847162247)]
+ edges = [(1, 2), (0, 3), (0, 4), (3, 5), (1, 6), (4, 6), (2, 7), (5, 7)]
+ mesh = ob.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+ mod = ob.modifiers.new("subsurf", 'SUBSURF')
+ mod.levels = 2
+
+ return [foot, pole, foot_roll]
+
diff --git a/rigify/rigs/copy.py b/rigify/rigs/copy.py
new file mode 100644
index 00000000..61c4cc99
--- /dev/null
+++ b/rigify/rigs/copy.py
@@ -0,0 +1,114 @@
+#====================== 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 rigify.utils import MetarigError
+from rigify.utils import copy_bone
+from rigify.utils import strip_org, make_deformer_name
+from rigify.utils import create_bone_widget
+
+
+class Rig:
+ """ A "copy" rig. All it does is duplicate the original bone and
+ constrain it.
+ This is a control and deformation rig.
+
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ """
+ self.obj = obj
+ self.org_bone = bone
+ self.org_name = strip_org(bone)
+ self.params = params
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Make a control bone (copy of original).
+ bone = copy_bone(self.obj, self.org_bone, self.org_name)
+
+ # Make a deformation bone (copy of original, child of original).
+ def_bone = copy_bone(self.obj, self.org_bone, make_deformer_name(self.org_name))
+
+ # Get edit bones
+ eb = self.obj.data.edit_bones
+ bone_e = eb[bone]
+ def_bone_e = eb[def_bone]
+
+ # Parent
+ def_bone_e.use_connect = False
+ def_bone_e.parent = eb[self.org_bone]
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ # Constrain the original bone.
+ con = pb[self.org_bone].constraints.new('COPY_TRANSFORMS')
+ con.name = "copy_loc"
+ con.target = self.obj
+ con.subtarget = bone
+
+ # Create control widget
+ create_bone_widget(self.obj, bone)
+
+ @classmethod
+ def create_sample(self, obj):
+ """ Create a sample metarig for this rig type.
+
+ """
+ # generated by rigify.utils.write_metarig
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bones = {}
+
+ bone = arm.edit_bones.new('Bone')
+ bone.head[:] = 0.0000, 0.0000, 0.0000
+ bone.tail[:] = 0.0000, 0.0000, 0.2000
+ bone.roll = 0.0000
+ bone.use_connect = False
+ bones['Bone'] = bone.name
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bones['Bone']]
+ pbone.rigify_type = 'copy'
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.rigify_parameters.add()
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in arm.edit_bones:
+ bone.select = False
+ bone.select_head = False
+ bone.select_tail = False
+ for b in bones:
+ bone = arm.edit_bones[bones[b]]
+ bone.select = True
+ bone.select_head = True
+ bone.select_tail = True
+ arm.edit_bones.active = bone
+
diff --git a/rigify/rigs/finger.py b/rigify/rigs/finger.py
new file mode 100644
index 00000000..f5788d02
--- /dev/null
+++ b/rigify/rigs/finger.py
@@ -0,0 +1,413 @@
+#====================== 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 Vector
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone
+from rigify.utils import connected_children_names
+from rigify.utils import strip_org, make_mechanism_name, make_deformer_name
+from rigify.utils import get_layers
+from rigify.utils import create_widget, create_line_widget, create_limb_widget
+from rna_prop_ui import rna_idprop_ui_prop_get
+import re
+
+
+class Rig:
+ """ A finger rig. It takes a single chain of bones.
+ This is a control and deformation rig.
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ """
+ self.obj = obj
+ self.org_bones = [bone] + connected_children_names(obj, bone)
+ self.params = params
+
+ if len(self.org_bones) <= 1:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones." % (strip_org(bone)))
+
+
+ # Get user-specified layers, if they exist
+ if params.separate_extra_layers:
+ self.ex_layers = list(params.extra_layers)
+ else:
+ self.ex_layers = None
+
+ # Get other rig parameters
+ self.primary_rotation_axis = params.primary_rotation_axis
+ self.use_digit_twist = params.use_digit_twist
+
+ def deform(self):
+ """ Generate the deformation rig.
+ Just a copy of the original bones, except the first digit which is a twist bone.
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Create the bones
+ # First bone is a twist bone
+ if self.use_digit_twist:
+ b1a = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".01")))
+ b1b = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0] + ".02")))
+ b1tip = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[0] + ".tip")))
+ else:
+ b1 = copy_bone(self.obj, self.org_bones[0], make_deformer_name(strip_org(self.org_bones[0])))
+
+ # The rest are normal
+ bones = []
+ for bone in self.org_bones[1:]:
+ bones += [copy_bone(self.obj, bone, make_deformer_name(strip_org(bone)))]
+
+ # Position bones
+ eb = self.obj.data.edit_bones
+ if self.use_digit_twist:
+ b1a_e = eb[b1a]
+ b1b_e = eb[b1b]
+ b1tip_e = eb[b1tip]
+
+ b1tip_e.use_connect = False
+ b1tip_e.tail += Vector((0.1, 0, 0))
+ b1tip_e.head = b1b_e.tail
+ b1tip_e.length = b1a_e.length / 4
+
+ center = (b1a_e.head + b1a_e.tail) / 2
+ b1a_e.tail = center
+ b1b_e.use_connect = False
+ b1b_e.head = center
+
+ # Parenting
+ if self.use_digit_twist:
+ b1b_e.parent = eb[self.org_bones[0]]
+ b1tip_e.parent = eb[self.org_bones[0]]
+ else:
+ eb[b1].use_connect = False
+ eb[b1].parent = eb[self.org_bones[0]]
+
+ for (ba, bb) in zip(bones, self.org_bones[1:]):
+ eb[ba].use_connect = False
+ eb[ba].parent = eb[bb]
+
+ # Constraints
+ if self.use_digit_twist:
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ b1a_p = pb[b1a]
+
+ con = b1a_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = self.org_bones[0]
+
+ con = b1a_p.constraints.new('COPY_SCALE')
+ con.name = "copy_scale"
+ con.target = self.obj
+ con.subtarget = self.org_bones[0]
+
+ con = b1a_p.constraints.new('DAMPED_TRACK')
+ con.name = "track_to"
+ con.target = self.obj
+ con.subtarget = b1tip
+
+ def control(self):
+ """ Generate the control rig.
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Figure out the name for the control bone (remove the last .##)
+ ctrl_name = re.sub("([0-9]+\.)", "", strip_org(self.org_bones[0])[::-1], count=1)[::-1]
+
+ # Create the bones
+ ctrl = copy_bone(self.obj, self.org_bones[0], ctrl_name)
+
+ helpers = []
+ bones = []
+ for bone in self.org_bones:
+ bones += [copy_bone(self.obj, bone, strip_org(bone))]
+ helpers += [copy_bone(self.obj, bone, make_mechanism_name(strip_org(bone)))]
+
+ # Position bones
+ eb = self.obj.data.edit_bones
+
+ length = 0.0
+ for bone in helpers:
+ length += eb[bone].length
+ eb[bone].length /= 2
+
+ eb[ctrl].length = length * 1.5
+
+ # Parent bones
+ prev = eb[self.org_bones[0]].parent
+ for (b, h) in zip(bones, helpers):
+ b_e = eb[b]
+ h_e = eb[h]
+ b_e.use_connect = False
+ h_e.use_connect = False
+
+ b_e.parent = h_e
+ h_e.parent = prev
+
+ prev = b_e
+
+ # Transform locks and rotation mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ for bone in bones[1:]:
+ pb[bone].lock_location = True, True, True
+
+ if pb[self.org_bones[0]].bone.use_connect == True:
+ pb[bones[0]].lock_location = True, True, True
+
+ pb[ctrl].lock_scale = True, False, True
+
+ for bone in helpers:
+ pb[bone].rotation_mode = 'XYZ'
+
+ # Drivers
+ i = 1
+ val = 1.2 / (len(self.org_bones) - 1)
+ for bone in helpers:
+ # Add custom prop
+ prop_name = "bend_%02d" % i
+ prop = rna_idprop_ui_prop_get(pb[ctrl], prop_name, create=True)
+ prop["min"] = 0.0
+ prop["max"] = 1.0
+ prop["soft_min"] = 0.0
+ prop["soft_max"] = 1.0
+ if i == 1:
+ pb[ctrl][prop_name] = 0.0
+ else:
+ pb[ctrl][prop_name] = val
+
+ # Add driver
+ if 'X' in self.primary_rotation_axis:
+ fcurve = pb[bone].driver_add("rotation_euler", 0)
+ elif 'Y' in self.primary_rotation_axis:
+ fcurve = pb[bone].driver_add("rotation_euler", 1)
+ else:
+ fcurve = pb[bone].driver_add("rotation_euler", 2)
+
+ driver = fcurve.driver
+ driver.type = 'SCRIPTED'
+
+ var = driver.variables.new()
+ var.name = "ctrl_y"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = pb[ctrl].path_from_id() + '.scale[1]'
+
+ var = driver.variables.new()
+ var.name = "bend"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = pb[ctrl].path_from_id() + '["' + prop_name + '"]'
+
+ if '-' in self.primary_rotation_axis:
+ driver.expression = "-(1.0-ctrl_y) * bend * 3.14159 * 2"
+ else:
+ driver.expression = "(1.0-ctrl_y) * bend * 3.14159 * 2"
+
+ i += 1
+
+ # Constraints
+ con = pb[helpers[0]].constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = ctrl
+
+ con = pb[helpers[0]].constraints.new('COPY_ROTATION')
+ con.name = "copy_rotation"
+ con.target = self.obj
+ con.subtarget = ctrl
+
+ # Constrain org bones to the control bones
+ for (bone, org) in zip(bones, self.org_bones):
+ con = pb[org].constraints.new('COPY_TRANSFORMS')
+ con.name = "copy_transforms"
+ con.target = self.obj
+ con.subtarget = bone
+
+ # Set layers for extra control bones
+ if self.ex_layers:
+ for bone in bones:
+ pb[bone].bone.layers = self.ex_layers
+
+ # Create control widgets
+ w = create_widget(self.obj, ctrl)
+ if w != None:
+ mesh = w.data
+ verts = [(0, 0, 0), (0, 1, 0), (0.05, 1, 0), (0.05, 1.1, 0), (-0.05, 1.1, 0), (-0.05, 1, 0)]
+ if 'Z' in self.primary_rotation_axis:
+ # Flip x/z coordinates
+ temp = []
+ for v in verts:
+ temp += [(v[2], v[1], v[0])]
+ verts = temp
+ edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+ for bone in bones:
+ create_limb_widget(self.obj, bone)
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+ """
+ self.deform()
+ self.control()
+
+ @classmethod
+ def add_parameters(self, group):
+ """ Add the parameters of this rig type to the
+ RigifyParameters IDPropertyGroup
+ """
+ items = [('X', 'X', ''), ('Y', 'Y', ''), ('Z', 'Z', ''), ('-X', '-X', ''), ('-Y', '-Y', ''), ('-Z', '-Z', '')]
+ group.primary_rotation_axis = bpy.props.EnumProperty(items=items, name="Primary Rotation Axis", default='X')
+
+ group.separate_extra_layers = bpy.props.BoolProperty(name="Separate Secondary Control Layers:", default=False, description="Enable putting the secondary controls on a separate layer from the primary controls.")
+ group.extra_layers = bpy.props.BoolVectorProperty(size=32, description="Layers for the secondary controls to be on.")
+
+ group.use_digit_twist = bpy.props.BoolProperty(name="Digit Twist", default=True, description="Generate the dual-bone twist setup for the first finger digit.")
+
+ @classmethod
+ def parameters_ui(self, layout, obj, bone):
+ """ Create the ui for the rig parameters.
+ """
+ params = obj.pose.bones[bone].rigify_parameters[0]
+
+ r = layout.row()
+ r.prop(params, "separate_extra_layers")
+
+ r = layout.row()
+ r.active = params.separate_extra_layers
+
+ col = r.column(align=True)
+ row = col.row(align=True)
+ row.prop(params, "extra_layers", index=0, toggle=True, text="")
+ row.prop(params, "extra_layers", index=1, toggle=True, text="")
+ row.prop(params, "extra_layers", index=2, toggle=True, text="")
+ row.prop(params, "extra_layers", index=3, toggle=True, text="")
+ row.prop(params, "extra_layers", index=4, toggle=True, text="")
+ row.prop(params, "extra_layers", index=5, toggle=True, text="")
+ row.prop(params, "extra_layers", index=6, toggle=True, text="")
+ row.prop(params, "extra_layers", index=7, toggle=True, text="")
+ row = col.row(align=True)
+ row.prop(params, "extra_layers", index=16, toggle=True, text="")
+ row.prop(params, "extra_layers", index=17, toggle=True, text="")
+ row.prop(params, "extra_layers", index=18, toggle=True, text="")
+ row.prop(params, "extra_layers", index=19, toggle=True, text="")
+ row.prop(params, "extra_layers", index=20, toggle=True, text="")
+ row.prop(params, "extra_layers", index=21, toggle=True, text="")
+ row.prop(params, "extra_layers", index=22, toggle=True, text="")
+ row.prop(params, "extra_layers", index=23, toggle=True, text="")
+
+ col = r.column(align=True)
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=8, toggle=True, text="")
+ row.prop(params, "ik_layers", index=9, toggle=True, text="")
+ row.prop(params, "ik_layers", index=10, toggle=True, text="")
+ row.prop(params, "ik_layers", index=11, toggle=True, text="")
+ row.prop(params, "ik_layers", index=12, toggle=True, text="")
+ row.prop(params, "ik_layers", index=13, toggle=True, text="")
+ row.prop(params, "ik_layers", index=14, toggle=True, text="")
+ row.prop(params, "ik_layers", index=15, toggle=True, text="")
+ row = col.row(align=True)
+ row.prop(params, "ik_layers", index=24, toggle=True, text="")
+ row.prop(params, "ik_layers", index=25, toggle=True, text="")
+ row.prop(params, "ik_layers", index=26, toggle=True, text="")
+ row.prop(params, "ik_layers", index=27, toggle=True, text="")
+ row.prop(params, "ik_layers", index=28, toggle=True, text="")
+ row.prop(params, "ik_layers", index=29, toggle=True, text="")
+ row.prop(params, "ik_layers", index=30, toggle=True, text="")
+ row.prop(params, "ik_layers", index=31, toggle=True, text="")
+
+ r = layout.row()
+ r.label(text="Bend rotation axis:")
+ r.prop(params, "primary_rotation_axis", text="")
+
+ col = layout.column()
+ col.prop(params, "use_digit_twist")
+
+ @classmethod
+ def create_sample(self, obj):
+ # generated by rigify.utils.write_metarig
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bones = {}
+
+ bone = arm.edit_bones.new('finger.01')
+ bone.head[:] = 0.0000, 0.0000, 0.0000
+ bone.tail[:] = 0.2529, 0.0000, 0.0000
+ bone.roll = 3.1416
+ bone.use_connect = False
+ bones['finger.01'] = bone.name
+ bone = arm.edit_bones.new('finger.02')
+ bone.head[:] = 0.2529, 0.0000, 0.0000
+ bone.tail[:] = 0.4024, 0.0000, -0.0264
+ bone.roll = -2.9671
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger.01']]
+ bones['finger.02'] = bone.name
+ bone = arm.edit_bones.new('finger.03')
+ bone.head[:] = 0.4024, 0.0000, -0.0264
+ bone.tail[:] = 0.4975, -0.0000, -0.0610
+ bone.roll = -2.7925
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['finger.02']]
+ bones['finger.03'] = bone.name
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bones['finger.01']]
+ pbone.rigify_type = 'finger'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YZX'
+ pbone.rigify_parameters.add()
+ pbone = obj.pose.bones[bones['finger.02']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YZX'
+ pbone = obj.pose.bones[bones['finger.03']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YZX'
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in arm.edit_bones:
+ bone.select = False
+ bone.select_head = False
+ bone.select_tail = False
+ for b in bones:
+ bone = arm.edit_bones[bones[b]]
+ bone.select = True
+ bone.select_head = True
+ bone.select_tail = True
+ arm.edit_bones.active = bone
+
diff --git a/rigify/rigs/misc/__init__.py b/rigify/rigs/misc/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/rigify/rigs/misc/__init__.py
diff --git a/rigify/rigs/misc/delta.py b/rigify/rigs/misc/delta.py
new file mode 100644
index 00000000..2157970a
--- /dev/null
+++ b/rigify/rigs/misc/delta.py
@@ -0,0 +1,161 @@
+#====================== 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 math import acos
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone
+from rigify.utils import org_name, make_mechanism_name
+
+
+class Rig:
+ """ A delta rig.
+ Creates a setup that will place its child at its position in pose mode,
+ but will not modifying its child's position in edit mode.
+ This is a mechanism-only rig (no control or deformation bones).
+
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ Store any data or references to data that will be needed later on.
+ In particular, store references to bones that will be needed.
+ Do NOT change any data in the scene. This is a gathering phase only.
+
+ """
+ bb = obj.data.bones
+
+ if bb[bone].children == None:
+ raise MetarigError("RIGIFY ERROR: bone '%s': rig type requires one child." % org_name(bone.name))
+ if bb[bone].use_connect == True:
+ raise MetarigError("RIGIFY ERROR: bone '%s': rig type cannot be connected to parent." % org_name(bone.name))
+
+ self.obj = obj
+ self.org_bones = {"delta": bone, "child": bb[bone].children[0].name}
+ self.org_names = [org_name(bone), org_name(bb[bone].children[0].name)]
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+ eb = self.obj.data.edit_bones
+
+ org_delta = self.org_bones["delta"]
+ org_delta_e = eb[self.org_bones["delta"]]
+ org_child = self.org_bones["child"]
+ org_child_e = eb[self.org_bones["child"]]
+
+ # Calculate the matrix for achieving the delta
+ child_mat = org_delta_e.matrix.invert() * org_child_e.matrix
+ mat = org_delta_e.matrix * child_mat.invert()
+
+ # Create the delta bones.
+ delta_e = eb[copy_bone(self.obj, self.org_bones["delta"])]
+ delta_e.name = make_mechanism_name(self.org_names[0])
+ delta = delta_e.name
+
+ # Set the delta to the matrix's transforms
+ set_mat(self.obj, delta, mat)
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Constrain org_delta to delta
+ con = self.obj.pose.bones[org_delta].constraints.new('COPY_TRANSFORMS')
+ con.name = "delta"
+ con.target = self.obj
+ con.subtarget = delta
+
+ @classmethod
+ def create_sample(self, obj):
+ # generated by rigify.utils.write_metarig
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bones = {}
+
+ bone = arm.edit_bones.new('delta')
+ bone.head[:] = 0.0000, -0.1198, 0.1253
+ bone.tail[:] = -0.0000, -0.2483, 0.2785
+ bone.roll = -0.0000
+ bone.use_connect = False
+ bones['delta'] = bone.name
+ bone = arm.edit_bones.new('Bone')
+ bone.head[:] = -0.0000, 0.0000, 0.0000
+ bone.tail[:] = -0.0000, 0.0000, 0.2000
+ bone.roll = 0.0000
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['delta']]
+ bones['Bone'] = bone.name
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bones['delta']]
+ pbone.rigify_type = 'misc.delta'
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.rigify_parameters.add()
+ pbone = obj.pose.bones[bones['Bone']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in arm.edit_bones:
+ bone.select = False
+ bone.select_head = False
+ bone.select_tail = False
+ for b in bones:
+ bone = arm.edit_bones[bones[b]]
+ bone.select = True
+ bone.select_head = True
+ bone.select_tail = True
+ arm.edit_bones.active = bone
+
+
+def set_mat(obj, bone_name, matrix):
+ """ Sets the bone to have the given transform matrix.
+ """
+ a = obj.data.edit_bones[bone_name]
+
+ a.head = (0, 0, 0)
+ a.tail = (0, 1, 0)
+
+ a.transform(matrix)
+
+ d = acos(a.matrix.to_quat().dot(matrix.to_quat())) * 2
+
+ roll_1 = a.roll + d
+ roll_2 = a.roll - d
+
+ a.roll = roll_1
+ d1 = a.matrix.to_quat().dot(matrix.to_quat())
+ a.roll = roll_2
+ d2 = a.matrix.to_quat().dot(matrix.to_quat())
+
+ if d1 > d2:
+ a.roll = roll_1
+ else:
+ a.roll = roll_2
+
diff --git a/rigify/rigs/neck_short.py b/rigify/rigs/neck_short.py
new file mode 100644
index 00000000..3c34fbda
--- /dev/null
+++ b/rigify/rigs/neck_short.py
@@ -0,0 +1,385 @@
+#====================== 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 Vector
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone, flip_bone, put_bone
+from rigify.utils import connected_children_names
+from rigify.utils import strip_org, make_mechanism_name, make_deformer_name
+from rigify.utils import obj_to_bone, create_circle_widget
+from rna_prop_ui import rna_idprop_ui_prop_get
+
+script1 = """
+head_neck = ["%s", "%s"]
+"""
+
+script2 = """
+if is_selected(head_neck[0]):
+ layout.prop(pose_bones[head_neck[0]], '["isolate"]', text="Isolate (" + head_neck[0] + ")", slider=True)
+"""
+
+script3 = """
+if is_selected(head_neck):
+ layout.prop(pose_bones[head_neck[0]], '["neck_follow"]', text="Neck Follow Head (" + head_neck[0] + ")", slider=True)
+"""
+
+
+class Rig:
+ """ A "spine" rig. It turns a chain of bones into a rig with two controls:
+ One for the hips, and one for the rib cage.
+
+ """
+ def __init__(self, obj, bone_name, params):
+ """ Gather and validate data about the rig.
+
+ """
+ self.obj = obj
+ self.org_bones = [bone_name] + connected_children_names(obj, bone_name)
+ self.params = params
+
+ if len(self.org_bones) <= 1:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones." % (strip_org(bone)))
+
+ self.isolate = False
+ if self.obj.data.bones[bone_name].parent:
+ self.isolate = True
+
+ def gen_deform(self):
+ """ Generate the deformation rig.
+
+ """
+ for name in self.org_bones:
+ bpy.ops.object.mode_set(mode='EDIT')
+ eb = self.obj.data.edit_bones
+
+ # Create deform bone
+ bone_e = eb[copy_bone(self.obj, name)]
+
+ # Change its name
+ bone_e.name = make_deformer_name(strip_org(name))
+ bone_name = bone_e.name
+
+ # Leave edit mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Get the pose bone
+ bone = self.obj.pose.bones[bone_name]
+
+ # Constrain to the original bone
+ con = bone.constraints.new('COPY_TRANSFORMS')
+ con.name = "copy_transforms"
+ con.target = self.obj
+ con.subtarget = name
+
+ def gen_control(self):
+ """ Generate the control rig.
+
+ """
+ #---------------------------------
+ # Create the neck and head controls
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Create bones
+ neck_ctrl = copy_bone(self.obj, self.org_bones[0], strip_org(self.org_bones[0]))
+ neck_follow = copy_bone(self.obj, self.org_bones[-1], make_mechanism_name(strip_org(self.org_bones[0] + ".follow")))
+
+ head_ctrl = copy_bone(self.obj, self.org_bones[-1], strip_org(self.org_bones[-1]))
+ head_mch = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[-1])))
+ if self.isolate:
+ head_socket1 = copy_bone(self.obj, self.org_bones[-1], make_mechanism_name(strip_org(self.org_bones[-1] + ".socket1")))
+ head_socket2 = copy_bone(self.obj, self.org_bones[-1], make_mechanism_name(strip_org(self.org_bones[-1] + ".socket2")))
+
+ # Create neck chain bones
+ neck = []
+ helpers = []
+ for name in self.org_bones:
+ neck += [copy_bone(self.obj, name, make_mechanism_name(strip_org(name)))]
+ helpers += [copy_bone(self.obj, neck_ctrl, make_mechanism_name(strip_org(name + ".02")))]
+
+ # Fetch edit bones
+ eb = self.obj.data.edit_bones
+
+ neck_ctrl_e = eb[neck_ctrl]
+ neck_follow_e = eb[neck_follow]
+ head_ctrl_e = eb[head_ctrl]
+ head_mch_e = eb[head_mch]
+ if self.isolate:
+ head_socket1_e = eb[head_socket1]
+ head_socket2_e = eb[head_socket2]
+
+ # Parenting
+ head_ctrl_e.use_connect = False
+ head_ctrl_e.parent = neck_ctrl_e.parent
+ head_mch_e.use_connect = False
+ head_mch_e.parent = head_ctrl_e
+
+ if self.isolate:
+ head_socket1_e.use_connect = False
+ head_socket1_e.parent = neck_ctrl_e.parent
+
+ head_socket2_e.use_connect = False
+ head_socket2_e.parent = None
+
+ head_ctrl_e.parent = head_socket2_e
+
+ for (name1, name2) in zip(neck, helpers):
+ eb[name1].use_connect = False
+ eb[name1].parent = eb[name2]
+ eb[name2].use_connect = False
+ eb[name2].parent = neck_ctrl_e.parent
+
+ neck_follow_e.use_connect = False
+ neck_follow_e.parent = neck_ctrl_e.parent
+ neck_ctrl_e.parent = neck_follow_e
+
+ # Position
+ put_bone(self.obj, neck_follow, neck_ctrl_e.head)
+ put_bone(self.obj, head_ctrl, neck_ctrl_e.head)
+ put_bone(self.obj, head_mch, neck_ctrl_e.head)
+ head_mch_e.length /= 2
+
+ if self.isolate:
+ put_bone(self.obj, head_socket1, neck_ctrl_e.head)
+ head_mch_e.length /= 2
+
+ put_bone(self.obj, head_socket2, neck_ctrl_e.head)
+ head_mch_e.length /= 3
+
+ for (name1, name2) in zip(neck, helpers):
+ put_bone(self.obj, name2, eb[name1].head)
+ eb[name2].length /= 3
+
+ # Switch to object mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+ neck_ctrl_p = pb[neck_ctrl]
+ neck_follow_p = pb[neck_follow]
+ head_ctrl_p = pb[head_ctrl]
+ if self.isolate:
+ head_socket1_p = pb[head_socket1]
+ head_socket2_p = pb[head_socket2]
+
+ # Custom bone appearance
+ neck_ctrl_p.custom_shape_transform = pb[self.org_bones[(len(self.org_bones) - 1) // 2]]
+ head_ctrl_p.custom_shape_transform = pb[self.org_bones[-1]]
+
+ # Custom properties
+ prop = rna_idprop_ui_prop_get(head_ctrl_p, "inf_extent", create=True)
+ head_ctrl_p["inf_extent"] = 0.5
+ prop["min"] = 0.0
+ prop["max"] = 1.0
+ prop["soft_min"] = 0.0
+ prop["soft_max"] = 1.0
+
+ prop = rna_idprop_ui_prop_get(head_ctrl_p, "neck_follow", create=True)
+ head_ctrl_p["neck_follow"] = 1.0
+ prop["min"] = 0.0
+ prop["max"] = 2.0
+ prop["soft_min"] = 0.0
+ prop["soft_max"] = 1.0
+
+ if self.isolate:
+ prop = rna_idprop_ui_prop_get(head_ctrl_p, "isolate", create=True)
+ head_ctrl_p["isolate"] = 0.0
+ prop["min"] = 0.0
+ prop["max"] = 1.0
+ prop["soft_min"] = 0.0
+ prop["soft_max"] = 1.0
+
+ # Constraints
+
+ # Neck follow
+ con = neck_follow_p.constraints.new('COPY_ROTATION')
+ con.name = "copy_rotation"
+ con.target = self.obj
+ con.subtarget = head_ctrl
+
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'SCRIPTED'
+ var.name = "follow"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = head_ctrl_p.path_from_id() + '["neck_follow"]'
+ driver.expression = "follow / 2"
+
+ # Isolate
+ if self.isolate:
+ con = head_socket2_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = head_socket1
+
+ con = head_socket2_p.constraints.new('COPY_TRANSFORMS')
+ con.name = "copy_transforms"
+ con.target = self.obj
+ con.subtarget = head_socket1
+
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'SCRIPTED'
+ var.name = "isolate"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = head_ctrl_p.path_from_id() + '["isolate"]'
+ driver.expression = "1.0 - isolate"
+
+ # Neck chain
+ first = True
+ prev = None
+ i = 0
+ l = len(neck)
+ for (name1, name2, org_name) in zip(neck, helpers, self.org_bones):
+ con = pb[org_name].constraints.new('COPY_TRANSFORMS')
+ con.name = "copy_transforms"
+ con.target = self.obj
+ con.subtarget = name1
+
+ n_con = pb[name2].constraints.new('COPY_TRANSFORMS')
+ n_con.name = "neck"
+ n_con.target = self.obj
+ n_con.subtarget = neck_ctrl
+
+ h_con = pb[name2].constraints.new('COPY_TRANSFORMS')
+ h_con.name = "head"
+ h_con.target = self.obj
+ h_con.subtarget = head_mch
+
+ con = pb[name2].constraints.new('COPY_LOCATION')
+ con.name = "anchor"
+ con.target = self.obj
+ if first:
+ con.subtarget = neck_ctrl
+ else:
+ con.subtarget = prev
+ con.head_tail = 1.0
+
+ # Drivers
+ n = (i + 1) / l
+
+ # Neck influence
+ fcurve = n_con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'SCRIPTED'
+ var.name = "ext"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = head_ctrl_p.path_from_id() + '["inf_extent"]'
+ driver.expression = "1.0 if (%.4f > (1.0-ext) or (1.0-ext) == 0.0) else (%.4f / (1.0-ext))" % (n, n)
+
+ # Head influence
+ if (i + 1) == l:
+ h_con.influence = 1.0
+ else:
+ fcurve = h_con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'SCRIPTED'
+ var.name = "ext"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = head_ctrl_p.path_from_id() + '["inf_extent"]'
+ driver.expression = "0.0 if (%.4f <= (1.0-ext)) else ((%.4f - (1.0-ext)) / ext)" % (n, n)
+
+ first = False
+ prev = name1
+ i += 1
+
+ # Create control widgets
+ w1 = create_circle_widget(self.obj, neck_ctrl, radius=1.0, head_tail=0.5)
+ w2 = create_circle_widget(self.obj, head_ctrl, radius=1.0, head_tail=0.5)
+
+ if w1 != None:
+ obj_to_bone(w1, self.obj, self.org_bones[(len(self.org_bones) - 1) // 2])
+ if w2 != None:
+ obj_to_bone(w2, self.obj, self.org_bones[-1])
+
+ # Return control bones
+ return (head_ctrl, neck_ctrl)
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ self.gen_deform()
+ (head, neck) = self.gen_control()
+
+ script = script1 % (head, neck)
+ if self.isolate:
+ script += script2
+ script += script3
+
+ return [script]
+
+ @classmethod
+ def create_sample(self, obj):
+ # generated by rigify.utils.write_metarig
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bones = {}
+
+ bone = arm.edit_bones.new('neck')
+ bone.head[:] = 0.0000, 0.0000, 0.0000
+ bone.tail[:] = 0.0000, -0.0500, 0.1500
+ bone.roll = 0.0000
+ bone.use_connect = False
+ bones['neck'] = bone.name
+ bone = arm.edit_bones.new('head')
+ bone.head[:] = 0.0000, -0.0500, 0.1500
+ bone.tail[:] = 0.0000, -0.0500, 0.4000
+ bone.roll = 3.1416
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['neck']]
+ bones['head'] = bone.name
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bones['neck']]
+ pbone.rigify_type = 'neck_short'
+ pbone.lock_location = (True, True, True)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone.rigify_parameters.add()
+ pbone = obj.pose.bones[bones['head']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in arm.edit_bones:
+ bone.select = False
+ bone.select_head = False
+ bone.select_tail = False
+ for b in bones:
+ bone = arm.edit_bones[bones[b]]
+ bone.select = True
+ bone.select_head = True
+ bone.select_tail = True
+ arm.edit_bones.active = bone
+
diff --git a/rigify/rigs/palm.py b/rigify/rigs/palm.py
new file mode 100644
index 00000000..5a0fa773
--- /dev/null
+++ b/rigify/rigs/palm.py
@@ -0,0 +1,273 @@
+#====================== 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 math import sin, cos, pi
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone
+from rigify.utils import strip_org, deformer, mch
+from rigify.utils import create_widget
+import re
+
+
+def bone_siblings(obj, bone):
+ """ Returns a list of the siblings of the given bone.
+ This requires that the bones has a parent.
+
+ """
+ parent = obj.data.bones[bone].parent
+
+ if parent == None:
+ return []
+
+ bones = []
+
+ for b in parent.children:
+ if b.name != bone:
+ bones += [b.name]
+
+ return bones
+
+
+def bone_distance(obj, bone1, bone2):
+ """ Returns the distance between two bones.
+
+ """
+ vec = obj.data.bones[bone1].head - obj.data.bones[bone2].head
+ return vec.length
+
+
+class Rig:
+ """ A "palm" rig. A set of sibling bones that bend with each other.
+ This is a control and deformation rig.
+
+ """
+ def __init__(self, obj, bone, params):
+ """ Gather and validate data about the rig.
+ """
+ self.obj = obj
+ self.params = params
+
+ siblings = bone_siblings(obj, bone)
+
+ if len(siblings) == 0:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': must have a parent and at least one sibling." % (strip_org(bone)))
+
+ # Sort list by name and distance
+ siblings.sort()
+ siblings.sort(key=lambda b: bone_distance(obj, bone, b))
+
+ self.org_bones = [bone] + siblings
+
+ # Get rig parameters
+ self.palm_rotation_axis = params.palm_rotation_axis
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Figure out the name for the control bone (remove the last .##)
+ last_bone = self.org_bones[-1:][0]
+ ctrl_name = re.sub("([0-9]+\.)", "", strip_org(last_bone)[::-1], count=1)[::-1]
+
+ # Make control bone
+ ctrl = copy_bone(self.obj, last_bone, ctrl_name)
+
+ # Make deformation bones
+ def_bones = []
+ for bone in self.org_bones:
+ b = copy_bone(self.obj, bone, deformer(strip_org(bone)))
+ def_bones += [b]
+
+ # Parenting
+ eb = self.obj.data.edit_bones
+
+ for d, b in zip(def_bones, self.org_bones):
+ eb[d].use_connect = False
+ eb[d].parent = eb[b]
+
+ # Constraints
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+
+ i = 0
+ div = len(self.org_bones) - 1
+ for b in self.org_bones:
+ con = pb[b].constraints.new('COPY_TRANSFORMS')
+ con.name = "copy_transforms"
+ con.target = self.obj
+ con.subtarget = ctrl
+ con.target_space = 'LOCAL'
+ con.owner_space = 'LOCAL'
+ con.influence = i / div
+
+ con = pb[b].constraints.new('COPY_ROTATION')
+ con.name = "copy_rotation"
+ con.target = self.obj
+ con.subtarget = ctrl
+ con.target_space = 'LOCAL'
+ con.owner_space = 'LOCAL'
+ if 'X' in self.palm_rotation_axis:
+ con.invert_x = True
+ con.use_x = True
+ con.use_z = False
+ else:
+ con.invert_z = True
+ con.use_x = False
+ con.use_z = True
+ con.use_y = False
+
+ con.influence = (i / div) - (1 - cos((i * pi / 2) / div))
+
+ i += 1
+
+ # Create control widget
+ w = create_widget(self.obj, ctrl)
+ if w != None:
+ mesh = w.data
+ verts = [(0.15780271589756012, 2.086162567138672e-07, -0.30000004172325134), (0.15780259668827057, 1.0, -0.2000001072883606), (-0.15780280530452728, 0.9999999403953552, -0.20000004768371582), (-0.15780259668827057, -2.086162567138672e-07, -0.29999998211860657), (-0.15780256688594818, -2.7089754439657554e-07, 0.30000004172325134), (-0.1578027755022049, 0.9999998807907104, 0.19999995827674866), (0.15780262649059296, 0.9999999403953552, 0.19999989867210388), (0.1578027456998825, 1.4633496903115883e-07, 0.29999998211860657), (0.15780268609523773, 0.2500001788139343, -0.27500003576278687), (-0.15780264139175415, 0.24999985098838806, -0.2749999761581421), (0.15780262649059296, 0.7500000596046448, -0.22500008344650269), (-0.1578027606010437, 0.7499998807907104, -0.2250000238418579), (0.15780265629291534, 0.75, 0.22499991953372955), (0.15780271589756012, 0.2500000596046448, 0.2749999761581421), (-0.15780261158943176, 0.2499997615814209, 0.27500003576278687), (-0.1578027307987213, 0.7499998807907104, 0.22499997913837433)]
+ if 'Z' in self.palm_rotation_axis:
+ # Flip x/z coordinates
+ temp = []
+ for v in verts:
+ temp += [(v[2], v[1], v[0])]
+ verts = temp
+ edges = [(1, 2), (0, 3), (4, 7), (5, 6), (8, 0), (9, 3), (10, 1), (11, 2), (12, 6), (13, 7), (4, 14), (15, 5), (10, 8), (11, 9), (15, 14), (12, 13)]
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+ mod = w.modifiers.new("subsurf", 'SUBSURF')
+ mod.levels = 2
+
+ @classmethod
+ def add_parameters(self, group):
+ """ Add the parameters of this rig type to the
+ RigifyParameters IDPropertyGroup
+
+ """
+ items = [('X', 'X', ''), ('Z', 'Z', '')]
+ group.palm_rotation_axis = bpy.props.EnumProperty(items=items, name="Palm Rotation Axis", default='X')
+
+ @classmethod
+ def parameters_ui(self, layout, obj, bone):
+ """ Create the ui for the rig parameters.
+
+ """
+ params = obj.pose.bones[bone].rigify_parameters[0]
+
+ r = layout.row()
+ r.label(text="Primary rotation axis:")
+ r.prop(params, "palm_rotation_axis", text="")
+
+ @classmethod
+ def create_sample(self, obj):
+ # generated by rigify.utils.write_metarig
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bones = {}
+
+ bone = arm.edit_bones.new('palm.parent')
+ bone.head[:] = 0.0000, 0.0000, 0.0000
+ bone.tail[:] = 0.0577, 0.0000, -0.0000
+ bone.roll = 3.1416
+ bone.use_connect = False
+ bones['palm.parent'] = bone.name
+ bone = arm.edit_bones.new('palm.04')
+ bone.head[:] = 0.0577, 0.0315, -0.0000
+ bone.tail[:] = 0.1627, 0.0315, -0.0000
+ bone.roll = 3.1416
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['palm.parent']]
+ bones['palm.04'] = bone.name
+ bone = arm.edit_bones.new('palm.03')
+ bone.head[:] = 0.0577, 0.0105, -0.0000
+ bone.tail[:] = 0.1627, 0.0105, -0.0000
+ bone.roll = 3.1416
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['palm.parent']]
+ bones['palm.03'] = bone.name
+ bone = arm.edit_bones.new('palm.02')
+ bone.head[:] = 0.0577, -0.0105, -0.0000
+ bone.tail[:] = 0.1627, -0.0105, -0.0000
+ bone.roll = 3.1416
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['palm.parent']]
+ bones['palm.02'] = bone.name
+ bone = arm.edit_bones.new('palm.01')
+ bone.head[:] = 0.0577, -0.0315, -0.0000
+ bone.tail[:] = 0.1627, -0.0315, -0.0000
+ bone.roll = 3.1416
+ bone.use_connect = False
+ bone.parent = arm.edit_bones[bones['palm.parent']]
+ bones['palm.01'] = bone.name
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bones['palm.parent']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone = obj.pose.bones[bones['palm.04']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone = obj.pose.bones[bones['palm.03']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone = obj.pose.bones[bones['palm.02']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone = obj.pose.bones[bones['palm.01']]
+ pbone.rigify_type = 'palm'
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, True, True)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'YXZ'
+ pbone.rigify_parameters.add()
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in arm.edit_bones:
+ bone.select = False
+ bone.select_head = False
+ bone.select_tail = False
+ for b in bones:
+ bone = arm.edit_bones[bones[b]]
+ bone.select = True
+ bone.select_head = True
+ bone.select_tail = True
+ arm.edit_bones.active = bone
+
diff --git a/rigify/rigs/spine.py b/rigify/rigs/spine.py
new file mode 100644
index 00000000..2318b3b9
--- /dev/null
+++ b/rigify/rigs/spine.py
@@ -0,0 +1,484 @@
+#====================== 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 Vector
+from rigify.utils import MetarigError
+from rigify.utils import copy_bone, flip_bone, put_bone
+from rigify.utils import connected_children_names
+from rigify.utils import strip_org, make_mechanism_name, make_deformer_name
+from rigify.utils import obj_to_bone, create_circle_widget
+from rna_prop_ui import rna_idprop_ui_prop_get
+
+
+script = """
+hips = "%s"
+ribs = "%s"
+if is_selected([hips, ribs]):
+ layout.prop(pose_bones[ribs], '["pivot_slide"]', text="Pivot Slide (" + ribs + ")", slider=True)
+if is_selected(ribs):
+ layout.prop(pose_bones[ribs], '["isolate"]', text="Isolate Rotation (" + ribs + ")", slider=True)
+"""
+
+
+class Rig:
+ """ A "spine" rig. It turns a chain of bones into a rig with two controls:
+ One for the hips, and one for the rib cage.
+
+ """
+ def __init__(self, obj, bone_name, params):
+ """ Gather and validate data about the rig.
+
+ """
+ self.obj = obj
+ self.org_bones = [bone_name] + connected_children_names(obj, bone_name)
+ self.params = params
+
+ if len(self.org_bones) <= 1:
+ raise MetarigError("RIGIFY ERROR: Bone '%s': input to rig type must be a chain of 2 or more bones." % (strip_org(bone)))
+
+ def gen_deform(self):
+ """ Generate the deformation rig.
+
+ """
+ for name in self.org_bones:
+ bpy.ops.object.mode_set(mode='EDIT')
+ eb = self.obj.data.edit_bones
+
+ # Create deform bone
+ bone_e = eb[copy_bone(self.obj, name)]
+
+ # Change its name
+ bone_e.name = make_deformer_name(strip_org(name))
+ bone_name = bone_e.name
+
+ # Leave edit mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Get the pose bone
+ bone = self.obj.pose.bones[bone_name]
+
+ # Constrain to the original bone
+ con = bone.constraints.new('COPY_TRANSFORMS')
+ con.name = "copy_transforms"
+ con.target = self.obj
+ con.subtarget = name
+
+ def gen_control(self):
+ """ Generate the control rig.
+
+ """
+ #---------------------------------
+ # Create the hip and rib controls
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # Copy org bones
+ hip_control = copy_bone(self.obj, self.org_bones[0], strip_org(self.org_bones[0]))
+ rib_control = copy_bone(self.obj, self.org_bones[-1], strip_org(self.org_bones[-1]))
+ rib_mch = copy_bone(self.obj, self.org_bones[-1], make_mechanism_name(strip_org(self.org_bones[-1] + ".follow")))
+ hinge = copy_bone(self.obj, self.org_bones[0], make_mechanism_name(strip_org(self.org_bones[-1]) + ".hinge"))
+
+ eb = self.obj.data.edit_bones
+
+ hip_control_e = eb[hip_control]
+ rib_control_e = eb[rib_control]
+ rib_mch_e = eb[rib_mch]
+ hinge_e = eb[hinge]
+
+ # Parenting
+ hip_control_e.use_connect = False
+ rib_control_e.use_connect = False
+ rib_mch_e.use_connect = False
+ hinge_e.use_connect = False
+
+ hinge_e.parent = None
+ rib_control_e.parent = hinge_e
+ rib_mch_e.parent = rib_control_e
+
+ # Position
+ flip_bone(self.obj, hip_control)
+ flip_bone(self.obj, hinge)
+
+ hinge_e.length /= 2
+ rib_mch_e.length /= 2
+
+ put_bone(self.obj, rib_control, hip_control_e.head)
+ put_bone(self.obj, rib_mch, hip_control_e.head)
+
+ bpy.ops.object.mode_set(mode='POSE')
+ bpy.ops.object.mode_set(mode='EDIT')
+ eb = self.obj.data.edit_bones
+
+ # Switch to object mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+ hip_control_p = pb[hip_control]
+ rib_control_p = pb[rib_control]
+ hinge_p = pb[hinge]
+
+ # No translation on rib control
+ rib_control_p.lock_location = [True, True, True]
+
+ # Hip does not use local location
+ hip_control_p.bone.use_local_location = False
+
+ # Custom hinge property
+ prop = rna_idprop_ui_prop_get(rib_control_p, "isolate", create=True)
+ rib_control_p["isolate"] = 1.0
+ prop["soft_min"] = prop["min"] = 0.0
+ prop["soft_max"] = prop["max"] = 1.0
+
+ # Constraints
+ con = hinge_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = hip_control
+
+ con1 = hinge_p.constraints.new('COPY_ROTATION')
+ con1.name = "isolate_off.01"
+ con1.target = self.obj
+ con1.subtarget = hip_control
+
+ con2 = rib_control_p.constraints.new('COPY_SCALE')
+ con2.name = "isolate_off.02"
+ con2.target = self.obj
+ con2.subtarget = hip_control
+ con2.use_offset = True
+ con2.target_space = 'LOCAL'
+ con2.owner_space = 'LOCAL'
+
+ # Drivers for "isolate_off"
+ fcurve = con1.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = rib_control_p.path_from_id() + '["isolate"]'
+ mod = fcurve.modifiers[0]
+ mod.poly_order = 1
+ mod.coefficients[0] = 1.0
+ mod.coefficients[1] = -1.0
+
+ fcurve = con2.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "var"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = rib_control_p.path_from_id() + '["isolate"]'
+ mod = fcurve.modifiers[0]
+ mod.poly_order = 1
+ mod.coefficients[0] = 1.0
+ mod.coefficients[1] = -1.0
+
+ # Appearence
+ hip_control_p.custom_shape_transform = pb[self.org_bones[0]]
+ rib_control_p.custom_shape_transform = pb[self.org_bones[-1]]
+
+ #-------------------------
+ # Create flex spine chain
+
+ # Create bones/parenting/positiong
+ bpy.ops.object.mode_set(mode='EDIT')
+ flex_bones = []
+ flex_helpers = []
+ prev_bone = None
+ for b in self.org_bones:
+ # Create bones
+ bone = copy_bone(self.obj, b, make_mechanism_name(strip_org(b) + ".flex"))
+ helper = copy_bone(self.obj, rib_mch, make_mechanism_name(strip_org(b) + ".flex_h"))
+ flex_bones += [bone]
+ flex_helpers += [helper]
+
+ eb = self.obj.data.edit_bones
+ bone_e = eb[bone]
+ helper_e = eb[helper]
+
+ # Parenting
+ bone_e.use_connect = False
+ helper_e.use_connect = False
+ if prev_bone == None:
+ helper_e.parent = eb[hip_control]
+ bone_e.parent = helper_e
+
+ # Position
+ put_bone(self.obj, helper, bone_e.head)
+ helper_e.length /= 4
+
+ prev_bone = bone
+
+ # Constraints
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+ rib_control_p = pb[rib_control]
+ rib_mch_p = pb[rib_mch]
+
+ inc = 1.0 / (len(flex_helpers) - 1)
+ inf = 1.0 / (len(flex_helpers) - 1)
+ for b in zip(flex_helpers[1:], flex_bones[:-1], self.org_bones[1:]):
+ bone_p = pb[b[0]]
+
+ # Scale constraints
+ con = bone_p.constraints.new('COPY_SCALE')
+ con.name = "copy_scale1"
+ con.target = self.obj
+ con.subtarget = flex_helpers[0]
+ con.influence = 1.0
+
+ con = bone_p.constraints.new('COPY_SCALE')
+ con.name = "copy_scale2"
+ con.target = self.obj
+ con.subtarget = rib_mch
+ con.influence = inf
+
+ # Bend constraints
+ con = bone_p.constraints.new('COPY_ROTATION')
+ con.name = "bend1"
+ con.target = self.obj
+ con.subtarget = flex_helpers[0]
+ con.influence = 1.0
+
+ con = bone_p.constraints.new('COPY_ROTATION')
+ con.name = "bend2"
+ con.target = self.obj
+ con.subtarget = rib_mch
+ con.influence = inf
+
+ # If not the rib control
+ if b[0] != flex_helpers[-1]:
+ # Custom bend property
+ prop_name = "bend_" + strip_org(b[2])
+ prop = rna_idprop_ui_prop_get(rib_control_p, prop_name, create=True)
+ rib_control_p[prop_name] = inf
+ prop["min"] = 0.0
+ prop["max"] = 1.0
+ prop["soft_min"] = 0.0
+ prop["soft_max"] = 1.0
+
+ # Bend driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = prop_name
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = rib_control_p.path_from_id() + '["' + prop_name + '"]'
+
+ # Location constraint
+ con = bone_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = b[1]
+ con.head_tail = 1.0
+
+ inf += inc
+
+ #----------------------------
+ # Create reverse spine chain
+
+ # Create bones/parenting/positioning
+ bpy.ops.object.mode_set(mode='EDIT')
+ rev_bones = []
+ prev_bone = None
+ for b in zip(flex_bones, self.org_bones):
+ # Create bones
+ bone = copy_bone(self.obj, b[1], make_mechanism_name(strip_org(b[1]) + ".reverse"))
+ rev_bones += [bone]
+ eb = self.obj.data.edit_bones
+ bone_e = eb[bone]
+
+ # Parenting
+ bone_e.use_connect = False
+ bone_e.parent = eb[b[0]]
+
+ # Position
+ flip_bone(self.obj, bone)
+ bone_e.tail = Vector(eb[b[0]].head)
+ #bone_e.head = Vector(eb[b[0]].tail)
+ if prev_bone == None:
+ pass # Position base bone wherever you want, for now do nothing (i.e. position at hips)
+ else:
+ put_bone(self.obj, bone, eb[prev_bone].tail)
+
+ prev_bone = bone
+
+ # Constraints
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pb = self.obj.pose.bones
+ prev_bone = None
+ for bone in rev_bones:
+ bone_p = pb[bone]
+
+ con = bone_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ if prev_bone == None:
+ con.subtarget = hip_control # Position base bone wherever you want, for now hips
+ else:
+ con.subtarget = prev_bone
+ con.head_tail = 1.0
+ prev_bone = bone
+
+ #---------------------------------------------
+ # Constrain org bones to flex bone's rotation
+ pb = self.obj.pose.bones
+ for b in zip(self.org_bones, flex_bones):
+ con = pb[b[0]].constraints.new('COPY_TRANSFORMS')
+ con.name = "copy_rotation"
+ con.target = self.obj
+ con.subtarget = b[1]
+
+ #---------------------------
+ # Create pivot slide system
+ pb = self.obj.pose.bones
+ bone_p = pb[self.org_bones[0]]
+ rib_control_p = pb[rib_control]
+
+ # Custom pivot_slide property
+ prop = rna_idprop_ui_prop_get(rib_control_p, "pivot_slide", create=True)
+ rib_control_p["pivot_slide"] = 1.0 / len(self.org_bones)
+ prop["min"] = 0.0
+ prop["max"] = 1.0
+ prop["soft_min"] = 1.0 / len(self.org_bones)
+ prop["soft_max"] = 1.0 - (1.0 / len(self.org_bones))
+
+ # Anchor constraint
+ con = bone_p.constraints.new('COPY_LOCATION')
+ con.name = "copy_location"
+ con.target = self.obj
+ con.subtarget = rev_bones[0]
+
+ # Slide constraints
+ i = 1
+ tot = len(rev_bones)
+ for rb in rev_bones:
+ con = bone_p.constraints.new('COPY_LOCATION')
+ con.name = "slide." + str(i)
+ con.target = self.obj
+ con.subtarget = rb
+ con.head_tail = 1.0
+
+ # Driver
+ fcurve = con.driver_add("influence")
+ driver = fcurve.driver
+ var = driver.variables.new()
+ driver.type = 'AVERAGE'
+ var.name = "slide"
+ var.targets[0].id_type = 'OBJECT'
+ var.targets[0].id = self.obj
+ var.targets[0].data_path = rib_control_p.path_from_id() + '["pivot_slide"]'
+ mod = fcurve.modifiers[0]
+ mod.poly_order = 1
+ mod.coefficients[0] = 1 - i
+ mod.coefficients[1] = tot
+
+ i += 1
+
+ # Create control widgets
+ w1 = create_circle_widget(self.obj, hip_control, radius=1.0, head_tail=1.0)
+ w2 = create_circle_widget(self.obj, rib_control, radius=1.0, head_tail=0.0)
+
+ if w1 != None:
+ obj_to_bone(w1, self.obj, self.org_bones[0])
+ if w2 != None:
+ obj_to_bone(w2, self.obj, self.org_bones[-1])
+
+ # Return control names
+ return hip_control, rib_control
+
+ def generate(self):
+ """ Generate the rig.
+ Do NOT modify any of the original bones, except for adding constraints.
+ The main armature should be selected and active before this is called.
+
+ """
+ self.gen_deform()
+ hips, ribs = self.gen_control()
+
+ return [script % (hips, ribs)]
+
+ @classmethod
+ def create_sample(self, obj):
+ # generated by rigify.utils.write_metarig
+ bpy.ops.object.mode_set(mode='EDIT')
+ arm = obj.data
+
+ bones = {}
+
+ bone = arm.edit_bones.new('hips')
+ bone.head[:] = 0.0000, 0.0000, 0.0000
+ bone.tail[:] = -0.0000, -0.0590, 0.2804
+ bone.roll = -0.0000
+ bone.use_connect = False
+ bones['hips'] = bone.name
+ bone = arm.edit_bones.new('spine')
+ bone.head[:] = -0.0000, -0.0590, 0.2804
+ bone.tail[:] = 0.0000, 0.0291, 0.5324
+ bone.roll = 0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['hips']]
+ bones['spine'] = bone.name
+ bone = arm.edit_bones.new('ribs')
+ bone.head[:] = 0.0000, 0.0291, 0.5324
+ bone.tail[:] = -0.0000, 0.0000, 1.0000
+ bone.roll = -0.0000
+ bone.use_connect = True
+ bone.parent = arm.edit_bones[bones['spine']]
+ bones['ribs'] = bone.name
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ pbone = obj.pose.bones[bones['hips']]
+ pbone.rigify_type = 'spine'
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone = obj.pose.bones[bones['spine']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone = obj.pose.bones[bones['ribs']]
+ pbone.rigify_type = ''
+ pbone.lock_location = (False, False, False)
+ pbone.lock_rotation = (False, False, False)
+ pbone.lock_rotation_w = False
+ pbone.lock_scale = (False, False, False)
+ pbone.rotation_mode = 'QUATERNION'
+ pbone = obj.pose.bones[bones['hips']]
+ pbone['rigify_type'] = 'spine'
+
+ bpy.ops.object.mode_set(mode='EDIT')
+ for bone in arm.edit_bones:
+ bone.select = False
+ bone.select_head = False
+ bone.select_tail = False
+ for b in bones:
+ bone = arm.edit_bones[bones[b]]
+ bone.select = True
+ bone.select_head = True
+ bone.select_tail = True
+ arm.edit_bones.active = bone
+
diff --git a/rigify/ui.py b/rigify/ui.py
new file mode 100644
index 00000000..a27d319b
--- /dev/null
+++ b/rigify/ui.py
@@ -0,0 +1,251 @@
+#====================== 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 rigify
+from rigify.utils import get_rig_type
+from rigify import generate
+from rna_prop_ui import rna_idprop_ui_prop_get
+
+
+class DATA_PT_rigify_buttons(bpy.types.Panel):
+ bl_label = "Rigify Buttons"
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "data"
+ #bl_options = {'DEFAULT_OPEN'}
+
+ @classmethod
+ def poll(cls, context):
+ if not context.armature:
+ return False
+ #obj = context.object
+ #if obj:
+ # return (obj.mode in ('POSE', 'OBJECT', 'EDIT'))
+ #return False
+ return True
+
+ def draw(self, context):
+ C = context
+ layout = self.layout
+ obj = context.object
+ id_store = C.window_manager
+
+ if obj.mode in ('POSE', 'OBJECT'):
+ row = layout.row()
+ row.operator("pose.rigify_generate", text="Generate")
+ elif obj.mode == 'EDIT':
+ # Build types list
+ collection_name = str(id_store.rigify_collection).replace(" ", "")
+
+ for i in range(0, len(id_store.rigify_types)):
+ id_store.rigify_types.remove(0)
+
+ for r in rigify.rig_list:
+ collection = r.split('.')[0]
+ if collection_name == "All":
+ a = id_store.rigify_types.add()
+ a.name = r
+ elif r.startswith(collection_name + '.'):
+ a = id_store.rigify_types.add()
+ a.name = r
+ elif collection_name == "None" and len(r.split('.')) == 1:
+ a = id_store.rigify_types.add()
+ a.name = r
+
+ ## Rig collection field
+ #row = layout.row()
+ #row.prop(id_store, 'rigify_collection', text="Category")
+
+ # Rig type list
+ row = layout.row()
+ row.template_list(id_store, "rigify_types", id_store, 'rigify_active_type')
+ row = layout.row()
+ op = row.operator("armature.metarig_sample_add", text="Add sample")
+ op.metarig_type = id_store.rigify_types[id_store.rigify_active_type].name
+
+
+class BONE_PT_rigify_buttons(bpy.types.Panel):
+ bl_label = "Rigify Type"
+ bl_space_type = 'PROPERTIES'
+ bl_region_type = 'WINDOW'
+ bl_context = "bone"
+ #bl_options = {'DEFAULT_OPEN'}
+
+ @classmethod
+ def poll(cls, context):
+ if not context.armature or not context.active_pose_bone:
+ return False
+ obj = context.object
+ if obj:
+ return (obj.mode in ('POSE'))
+ return False
+
+ def draw(self, context):
+ C = context
+ id_store = C.window_manager
+ bone = context.active_pose_bone
+ collection_name = str(id_store.rigify_collection).replace(" ", "")
+ rig_name = str(context.active_pose_bone.rigify_type).replace(" ", "")
+
+ layout = self.layout
+
+ # Build types list
+ for i in range(0, len(id_store.rigify_types)):
+ id_store.rigify_types.remove(0)
+
+ for r in rigify.rig_list:
+ collection = r.split('.')[0]
+ if collection_name == "All":
+ a = id_store.rigify_types.add()
+ a.name = r
+ elif r.startswith(collection_name + '.'):
+ a = id_store.rigify_types.add()
+ a.name = r
+ elif collection_name == "None" and len(r.split('.')) == 1:
+ a = id_store.rigify_types.add()
+ a.name = r
+
+ # Rig type field
+ row = layout.row()
+ row.prop_search(bone, "rigify_type", id_store, "rigify_types", text="Rig type:")
+
+ # Rig type parameters / Rig type non-exist alert
+ if rig_name != "":
+ if len(bone.rigify_parameters) < 1:
+ bone.rigify_parameters.add()
+
+ try:
+ rig = get_rig_type(rig_name)
+ rig.Rig
+ except (ImportError, AttributeError):
+ row = layout.row()
+ box = row.box()
+ box.label(text="ALERT: type \"%s\" does not exist!" % rig_name)
+ else:
+ try:
+ rig.Rig.parameters_ui
+ except AttributeError:
+ pass
+ else:
+ col = layout.column()
+ col.label(text="Options:")
+ box = layout.box()
+
+ rig.Rig.parameters_ui(box, C.active_object, bone.name)
+
+
+#class INFO_MT_armature_metarig_add(bpy.types.Menu):
+# bl_idname = "INFO_MT_armature_metarig_add"
+# bl_label = "Meta-Rig"
+
+# def draw(self, context):
+ #import rigify
+
+ #layout = self.layout
+ #layout.operator_context = 'INVOKE_REGION_WIN'
+
+ #for submodule_type in rigify.get_submodule_types():
+ # text = bpy.path.display_name(submodule_type)
+ # layout.operator("pose.metarig_sample_add", text=text, icon='OUTLINER_OB_ARMATURE').metarig_type = submodule_type
+
+
+def rigify_report_exception(operator, exception):
+ import traceback
+ import sys
+ import os
+ # find the module name where the error happened
+ # hint, this is the metarig type!
+ exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
+ fn = traceback.extract_tb(exceptionTraceback)[-1][0]
+ fn = os.path.basename(fn)
+ fn = os.path.splitext(fn)[0]
+ message = []
+ if fn.startswith("__"):
+ message.append("Incorrect armature...")
+ else:
+ message.append("Incorrect armature for type '%s'" % fn)
+ message.append(exception.message)
+
+ message.reverse() # XXX - stupid! menu's are upside down!
+
+ operator.report(set(['INFO']), '\n'.join(message))
+
+
+class Generate(bpy.types.Operator):
+ '''Generates a rig from the active metarig armature'''
+
+ bl_idname = "pose.rigify_generate"
+ bl_label = "Rigify Generate Rig"
+
+ def execute(self, context):
+ import imp
+ imp.reload(generate)
+
+ try:
+ generate.generate_rig(context, context.object)
+ except rigify.utils.MetarigError as rig_exception:
+ rigify_report_exception(self, rig_exception)
+
+ return {'FINISHED'}
+
+
+class Sample(bpy.types.Operator):
+ '''Create a sample metarig to be modified before generating the final rig.'''
+
+ bl_idname = "armature.metarig_sample_add"
+ bl_label = "Add a sample metarig for a rig type"
+
+ metarig_type = StringProperty(name="Type", description="Name of the rig type to generate a sample of", maxlen=128, default="")
+
+ def execute(self, context):
+ if context.mode == 'EDIT_ARMATURE' and self.metarig_type != "":
+ try:
+ rig = get_rig_type(self.metarig_type).Rig
+ create_sample = rig.create_sample
+ except (ImportError, AttributeError):
+ print("Rigify: rig type has no sample.")
+ else:
+ create_sample(context.active_object)
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ return {'FINISHED'}
+
+
+#menu_func = (lambda self, context: self.layout.menu("INFO_MT_armature_metarig_add", icon='OUTLINER_OB_ARMATURE'))
+
+#import space_info # ensure the menu is loaded first
+
+def register():
+ #bpy.types.register(DATA_PT_rigify_buttons)
+ #bpy.types.register(BONE_PT_rigify_buttons)
+ #bpy.types.register(Generate)
+ #bpy.types.register(Sample)
+
+ #space_info.INFO_MT_armature_add.append(ui.menu_func)
+ pass
+
+
+def unregister():
+ #bpy.types.unregister(DATA_PT_rigify_buttons)
+ #bpy.types.unregister(BONE_PT_rigify_buttons)
+ #bpy.types.unregister(Generate)
+ #bpy.types.unregister(Sample)
+ pass
+
diff --git a/rigify/utils.py b/rigify/utils.py
new file mode 100644
index 00000000..84d4c4f1
--- /dev/null
+++ b/rigify/utils.py
@@ -0,0 +1,497 @@
+#====================== 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
+import imp
+from random import randint
+from mathutils import Vector
+from math import ceil, floor
+from rna_prop_ui import rna_idprop_ui_prop_get
+
+RIG_DIR = "rigs" # Name of the directory where rig types are kept
+
+ORG_PREFIX = "ORG-" # Prefix of original bones.
+MCH_PREFIX = "MCH-" # Prefix of mechanism bones.
+DEF_PREFIX = "DEF-" # Prefix of deformation bones.
+WGT_PREFIX = "WGT-" # Prefix for widget objects
+ROOT_NAME = "root" # Name of the root bone.
+
+WGT_LAYERS = [x == 19 for x in range(0, 20)] # Widgets go on the last scene layer.
+
+MODULE_NAME = "rigify" # Windows/Mac blender is weird, so __package__ doesn't work
+
+
+#=======================================================================
+# Error handling
+#=======================================================================
+class MetarigError(Exception):
+ """ Exception raised for errors.
+ """
+ def __init__(self, message):
+ self.message = message
+
+ def __str__(self):
+ return repr(self.message)
+
+
+#=======================================================================
+# Name manipulation
+#=======================================================================
+def org_name(name):
+ """ Returns the name with ORG_PREFIX stripped from it.
+ """
+ if name.startswith(ORG_PREFIX):
+ return name[len(ORG_PREFIX):]
+ else:
+ return name
+
+
+def strip_org(name):
+ """ Returns the name with ORG_PREFIX stripped from it.
+ """
+ if name.startswith(ORG_PREFIX):
+ return name[len(ORG_PREFIX):]
+ else:
+ return name
+org_name = strip_org
+
+
+def org(name):
+ """ Prepends the ORG_PREFIX to a name if it doesn't already have
+ it, and returns it.
+ """
+ if name.startswith(ORG_PREFIX):
+ return name
+ else:
+ return ORG_PREFIX + name
+make_original_name = org
+
+
+def mch(name):
+ """ Prepends the MCH_PREFIX to a name if it doesn't already have
+ it, and returns it.
+ """
+ if name.startswith(MCH_PREFIX):
+ return name
+ else:
+ return MCH_PREFIX + name
+make_mechanism_name = mch
+
+
+def deformer(name):
+ """ Prepends the DEF_PREFIX to a name if it doesn't already have
+ it, and returns it.
+ """
+ if name.startswith(DEF_PREFIX):
+ return name
+ else:
+ return DEF_PREFIX + name
+make_deformer_name = deformer
+
+
+def insert_before_lr(name, text):
+ if name[-1] in ['l', 'L', 'r', 'R'] and name[-2] in ['.', '-', '_']:
+ return name[:-2] + text + name[-2:]
+ else:
+ return name + text
+
+
+#=======================
+# Bone manipulation
+#=======================
+def new_bone(obj, bone_name):
+ """ Adds a new bone to the given armature object.
+ Returns the resulting bone's name.
+ """
+ if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE':
+ edit_bone = obj.data.edit_bones.new(bone_name)
+ name = edit_bone.name
+ edit_bone.head = (0, 0, 0)
+ edit_bone.tail = (0, 1, 0)
+ edit_bone.roll = 0
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.mode_set(mode='EDIT')
+ return name
+ else:
+ raise MetarigError("Can't add new bone '%s' outside of edit mode." % bone_name)
+
+
+def copy_bone(obj, bone_name, assign_name=''):
+ """ Makes a copy of the given bone in the given armature object.
+ Returns the resulting bone's name.
+ """
+ if bone_name not in obj.data.bones:
+ raise MetarigError("copy_bone(): bone '%s' not found, cannot copy it." % bone_name)
+
+ if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE':
+ if assign_name == '':
+ assign_name = bone_name
+ # Copy the edit bone
+ edit_bone_1 = obj.data.edit_bones[bone_name]
+ edit_bone_2 = obj.data.edit_bones.new(assign_name)
+ bone_name_1 = bone_name
+ bone_name_2 = edit_bone_2.name
+
+ edit_bone_2.parent = edit_bone_1.parent
+ edit_bone_2.use_connect = edit_bone_1.use_connect
+
+ # Copy edit bone attributes
+ edit_bone_2.layers = list(edit_bone_1.layers)
+
+ edit_bone_2.head = Vector(edit_bone_1.head)
+ edit_bone_2.tail = Vector(edit_bone_1.tail)
+ edit_bone_2.roll = edit_bone_1.roll
+
+ edit_bone_2.use_inherit_rotation = edit_bone_1.use_inherit_rotation
+ edit_bone_2.use_inherit_scale = edit_bone_1.use_inherit_scale
+ edit_bone_2.use_local_location = edit_bone_1.use_local_location
+
+ edit_bone_2.use_deform = edit_bone_1.use_deform
+ edit_bone_2.bbone_segments = edit_bone_1.bbone_segments
+ edit_bone_2.bbone_in = edit_bone_1.bbone_in
+ edit_bone_2.bbone_out = edit_bone_1.bbone_out
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ # Get the pose bones
+ pose_bone_1 = obj.pose.bones[bone_name_1]
+ pose_bone_2 = obj.pose.bones[bone_name_2]
+
+ # Copy pose bone attributes
+ pose_bone_2.rotation_mode = pose_bone_1.rotation_mode
+ pose_bone_2.rotation_axis_angle = tuple(pose_bone_1.rotation_axis_angle)
+ pose_bone_2.rotation_euler = tuple(pose_bone_1.rotation_euler)
+ pose_bone_2.rotation_quaternion = tuple(pose_bone_1.rotation_quaternion)
+
+ pose_bone_2.lock_location = tuple(pose_bone_1.lock_location)
+ pose_bone_2.lock_scale = tuple(pose_bone_1.lock_scale)
+ pose_bone_2.lock_rotation = tuple(pose_bone_1.lock_rotation)
+ pose_bone_2.lock_rotation_w = pose_bone_1.lock_rotation_w
+ pose_bone_2.lock_rotations_4d = pose_bone_1.lock_rotations_4d
+
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ return bone_name_2
+ else:
+ raise MetarigError("Cannot copy bones outside of edit mode.")
+
+
+def flip_bone(obj, bone_name):
+ """ Flips an edit bone.
+ """
+ if bone_name not in obj.data.bones:
+ raise MetarigError("flip_bone(): bone '%s' not found, cannot copy it." % bone_name)
+
+ if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE':
+ bone = obj.data.edit_bones[bone_name]
+ head = Vector(bone.head)
+ tail = Vector(bone.tail)
+ bone.tail = head + tail
+ bone.head = tail
+ bone.tail = head
+ else:
+ raise MetarigError("Cannot flip bones outside of edit mode.")
+
+
+def put_bone(obj, bone_name, pos):
+ """ Places a bone at the given position.
+ """
+ if bone_name not in obj.data.bones:
+ raise MetarigError("put_bone(): bone '%s' not found, cannot copy it." % bone_name)
+
+ if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE':
+ bone = obj.data.edit_bones[bone_name]
+
+ delta = pos - bone.head
+ bone.translate(delta)
+ else:
+ raise MetarigError("Cannot 'put' bones outside of edit mode.")
+
+
+#=============================================
+# Widget creation
+#=============================================
+
+def obj_to_bone(obj, rig, bone_name):
+ """ Places an object at the location/rotation/scale of the given bone.
+ """
+ if bpy.context.mode == 'EDIT_ARMATURE':
+ raise MetarigError("obj_to_bone(): does not work while in edit mode.")
+
+ bone = rig.data.bones[bone_name]
+
+ mat = rig.matrix_world * bone.matrix_local
+
+ obj.location = mat.translation_part()
+
+ obj.rotation_mode = 'XYZ'
+ obj.rotation_euler = mat.to_euler()
+
+ scl = mat.scale_part()
+ scl_avg = (scl[0] + scl[1] + scl[2]) / 3
+ obj.scale = (bone.length * scl_avg), (bone.length * scl_avg), (bone.length * scl_avg)
+
+
+def create_widget(rig, bone_name):
+ """ Creates an empty widget object for a bone, and returns the object.
+ """
+ obj_name = WGT_PREFIX + bone_name
+ scene = bpy.context.scene
+ # Check if it already exists
+ if obj_name in scene.objects:
+ return None
+ else:
+ mesh = bpy.data.meshes.new(obj_name)
+ obj = bpy.data.objects.new(obj_name, mesh)
+ scene.objects.link(obj)
+
+ obj_to_bone(obj, rig, bone_name)
+ obj.layers = WGT_LAYERS
+
+ return obj
+
+
+# Common Widgets
+
+def create_line_widget(rig, bone_name):
+ """ Creates a basic line widget, a line that spans the length of the bone.
+ """
+ obj = create_widget(rig, bone_name)
+ if obj != None:
+ mesh = obj.data
+ mesh.from_pydata([(0, 0, 0), (0, 1, 0)], [(0, 1)], [])
+ mesh.update()
+
+
+def create_circle_widget(rig, bone_name, radius=1.0, head_tail=0.0):
+ """ Creates a basic circle widget, a circle around the y-axis.
+ radius: the radius of the circle
+ head_tail: where along the length of the bone the circle is (0.0=head, 1.0=tail)
+ """
+ obj = create_widget(rig, bone_name)
+ if obj != None:
+ v = [(0.7071068286895752, 2.980232238769531e-07, -0.7071065306663513), (0.8314696550369263, 2.980232238769531e-07, -0.5555699467658997), (0.9238795042037964, 2.682209014892578e-07, -0.3826831877231598), (0.9807852506637573, 2.5331974029541016e-07, -0.19509011507034302), (1.0, 2.365559055306221e-07, 1.6105803979371558e-07), (0.9807853698730469, 2.2351741790771484e-07, 0.19509044289588928), (0.9238796234130859, 2.086162567138672e-07, 0.38268351554870605), (0.8314696550369263, 1.7881393432617188e-07, 0.5555704236030579), (0.7071068286895752, 1.7881393432617188e-07, 0.7071070075035095), (0.5555702447891235, 1.7881393432617188e-07, 0.8314698934555054), (0.38268327713012695, 1.7881393432617188e-07, 0.923879861831665), (0.19509008526802063, 1.7881393432617188e-07, 0.9807855486869812), (-3.2584136988589307e-07, 1.1920928955078125e-07, 1.000000238418579), (-0.19509072601795197, 1.7881393432617188e-07, 0.9807854294776917), (-0.3826838731765747, 1.7881393432617188e-07, 0.9238795638084412), (-0.5555707216262817, 1.7881393432617188e-07, 0.8314695358276367), (-0.7071071863174438, 1.7881393432617188e-07, 0.7071065902709961), (-0.8314700126647949, 1.7881393432617188e-07, 0.5555698871612549), (-0.923879861831665, 2.086162567138672e-07, 0.3826829195022583), (-0.9807853698730469, 2.2351741790771484e-07, 0.1950896978378296), (-1.0, 2.365559907957504e-07, -7.290432222362142e-07), (-0.9807850122451782, 2.5331974029541016e-07, -0.195091113448143), (-0.9238790273666382, 2.682209014892578e-07, -0.38268423080444336), (-0.831468939781189, 2.980232238769531e-07, -0.5555710196495056), (-0.7071058750152588, 2.980232238769531e-07, -0.707107424736023), (-0.555569052696228, 2.980232238769531e-07, -0.8314701318740845), (-0.38268208503723145, 2.980232238769531e-07, -0.923879861831665), (-0.19508881866931915, 2.980232238769531e-07, -0.9807853102684021), (1.6053570561780361e-06, 2.980232238769531e-07, -0.9999997615814209), (0.19509197771549225, 2.980232238769531e-07, -0.9807847142219543), (0.3826850652694702, 2.980232238769531e-07, -0.9238786101341248), (0.5555717945098877, 2.980232238769531e-07, -0.8314683437347412)]
+ verts = [(a[0] * radius, head_tail, a[2] * radius) for a in v]
+ edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (15, 16), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (0, 31)]
+ mesh = obj.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+ return obj
+ else:
+ return None
+
+
+def create_sphere_widget(rig, bone_name):
+ """ Creates a basic sphere widget, three pependicular overlapping circles.
+ """
+ obj = create_widget(rig, bone_name)
+ if obj != None:
+ verts = [(0.3535533845424652, 0.3535533845424652, 0.0), (0.4619397521018982, 0.19134171307086945, 0.0), (0.5, -2.1855694143368964e-08, 0.0), (0.4619397521018982, -0.19134175777435303, 0.0), (0.3535533845424652, -0.3535533845424652, 0.0), (0.19134174287319183, -0.4619397521018982, 0.0), (7.549790126404332e-08, -0.5, 0.0), (-0.1913416087627411, -0.46193981170654297, 0.0), (-0.35355329513549805, -0.35355350375175476, 0.0), (-0.4619397521018982, -0.19134178757667542, 0.0), (-0.5, 5.962440319251527e-09, 0.0), (-0.4619397222995758, 0.1913418024778366, 0.0), (-0.35355326533317566, 0.35355350375175476, 0.0), (-0.19134148955345154, 0.46193987131118774, 0.0), (3.2584136988589307e-07, 0.5, 0.0), (0.1913420855998993, 0.46193960309028625, 0.0), (7.450580596923828e-08, 0.46193960309028625, 0.19134199619293213), (5.9254205098113744e-08, 0.5, 2.323586443253589e-07), (4.470348358154297e-08, 0.46193987131118774, -0.1913415789604187), (2.9802322387695312e-08, 0.35355350375175476, -0.3535533547401428), (2.9802322387695312e-08, 0.19134178757667542, -0.46193981170654297), (5.960464477539063e-08, -1.1151834122813398e-08, -0.5000000596046448), (5.960464477539063e-08, -0.1913418024778366, -0.46193984150886536), (5.960464477539063e-08, -0.35355350375175476, -0.3535533845424652), (7.450580596923828e-08, -0.46193981170654297, -0.19134166836738586), (9.348272556053416e-08, -0.5, 1.624372103492533e-08), (1.043081283569336e-07, -0.4619397521018982, 0.19134168326854706), (1.1920928955078125e-07, -0.3535533845424652, 0.35355329513549805), (1.1920928955078125e-07, -0.19134174287319183, 0.46193966269493103), (1.1920928955078125e-07, -4.7414250303745575e-09, 0.49999991059303284), (1.1920928955078125e-07, 0.19134172797203064, 0.46193966269493103), (8.940696716308594e-08, 0.3535533845424652, 0.35355329513549805), (0.3535534739494324, 0.0, 0.35355329513549805), (0.1913418173789978, -2.9802322387695312e-08, 0.46193966269493103), (8.303572940349113e-08, -5.005858838558197e-08, 0.49999991059303284), (-0.19134165346622467, -5.960464477539063e-08, 0.46193966269493103), (-0.35355329513549805, -8.940696716308594e-08, 0.35355329513549805), (-0.46193963289260864, -5.960464477539063e-08, 0.19134168326854706), (-0.49999991059303284, -5.960464477539063e-08, 1.624372103492533e-08), (-0.4619397521018982, -2.9802322387695312e-08, -0.19134166836738586), (-0.3535534143447876, -2.9802322387695312e-08, -0.3535533845424652), (-0.19134171307086945, 0.0, -0.46193984150886536), (7.662531942287387e-08, 9.546055501630235e-09, -0.5000000596046448), (0.19134187698364258, 5.960464477539063e-08, -0.46193981170654297), (0.3535535931587219, 5.960464477539063e-08, -0.3535533547401428), (0.4619399905204773, 5.960464477539063e-08, -0.1913415789604187), (0.5000000596046448, 5.960464477539063e-08, 2.323586443253589e-07), (0.4619396924972534, 2.9802322387695312e-08, 0.19134199619293213)]
+ edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 6), (6, 7), (7, 8), (8, 9), (9, 10), (10, 11), (11, 12), (12, 13), (13, 14), (14, 15), (0, 15), (16, 31), (16, 17), (17, 18), (18, 19), (19, 20), (20, 21), (21, 22), (22, 23), (23, 24), (24, 25), (25, 26), (26, 27), (27, 28), (28, 29), (29, 30), (30, 31), (32, 33), (33, 34), (34, 35), (35, 36), (36, 37), (37, 38), (38, 39), (39, 40), (40, 41), (41, 42), (42, 43), (43, 44), (44, 45), (45, 46), (46, 47), (32, 47)]
+ mesh = obj.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+
+def create_limb_widget(rig, bone_name):
+ """ Creates a basic limb widget, a line that spans the length of the
+ bone, with a circle around the center.
+ """
+ obj = create_widget(rig, bone_name)
+ if obj != None:
+ verts = [(-1.1920928955078125e-07, 1.7881393432617188e-07, 0.0), (3.5762786865234375e-07, 1.0000004768371582, 0.0), (0.1767769455909729, 0.5000001192092896, 0.17677664756774902), (0.20786768198013306, 0.5000001192092896, 0.1388925313949585), (0.23097014427185059, 0.5000001192092896, 0.09567084908485413), (0.24519658088684082, 0.5000001192092896, 0.048772573471069336), (0.2500002384185791, 0.5000001192092896, -2.545945676502015e-09), (0.24519658088684082, 0.5000001192092896, -0.048772573471069336), (0.23097014427185059, 0.5000001192092896, -0.09567084908485413), (0.20786768198013306, 0.5000001192092896, -0.13889259099960327), (0.1767769455909729, 0.5000001192092896, -0.1767767071723938), (0.13889282941818237, 0.5000001192092896, -0.20786744356155396), (0.09567105770111084, 0.5000001192092896, -0.23096990585327148), (0.04877278208732605, 0.5000001192092896, -0.24519634246826172), (1.7279069197684294e-07, 0.5000000596046448, -0.25), (-0.0487724244594574, 0.5000001192092896, -0.24519634246826172), (-0.09567070007324219, 0.5000001192092896, -0.2309698462486267), (-0.13889241218566895, 0.5000001192092896, -0.20786738395690918), (-0.17677652835845947, 0.5000001192092896, -0.17677664756774902), (-0.20786726474761963, 0.5000001192092896, -0.13889244198799133), (-0.23096972703933716, 0.5000001192092896, -0.09567070007324219), (-0.24519610404968262, 0.5000001192092896, -0.04877239465713501), (-0.2499997615814209, 0.5000001192092896, 2.1997936983098043e-07), (-0.24519598484039307, 0.5000001192092896, 0.04877282679080963), (-0.23096948862075806, 0.5000001192092896, 0.09567108750343323), (-0.20786696672439575, 0.5000001192092896, 0.1388927698135376), (-0.1767762303352356, 0.5000001192092896, 0.17677688598632812), (-0.13889199495315552, 0.5000001192092896, 0.2078675627708435), (-0.09567028284072876, 0.5000001192092896, 0.23097002506256104), (-0.048771947622299194, 0.5000001192092896, 0.24519634246826172), (6.555903269145347e-07, 0.5000001192092896, 0.25), (0.04877324402332306, 0.5000001192092896, 0.24519622325897217), (0.09567153453826904, 0.5000001192092896, 0.23096966743469238), (0.13889318704605103, 0.5000001192092896, 0.20786714553833008)]
+ edges = [(0, 1), (2, 3), (4, 3), (5, 4), (5, 6), (6, 7), (8, 7), (8, 9), (10, 9), (10, 11), (11, 12), (13, 12), (14, 13), (14, 15), (16, 15), (16, 17), (17, 18), (19, 18), (19, 20), (21, 20), (21, 22), (22, 23), (24, 23), (25, 24), (25, 26), (27, 26), (27, 28), (29, 28), (29, 30), (30, 31), (32, 31), (32, 33), (2, 33)]
+ mesh = obj.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+
+def create_bone_widget(rig, bone_name):
+ """ Creates a basic bone widget, a simple obolisk-esk shape.
+ """
+ obj = create_widget(rig, bone_name)
+ if obj != None:
+ verts = [(0.04, 1.0, -0.04), (0.1, 0.0, -0.1), (-0.1, 0.0, -0.1), (-0.04, 1.0, -0.04), (0.04, 1.0, 0.04), (0.1, 0.0, 0.1), (-0.1, 0.0, 0.1), (-0.04, 1.0, 0.04)]
+ edges = [(1, 2), (0, 1), (0, 3), (2, 3), (4, 5), (5, 6), (6, 7), (4, 7), (1, 5), (0, 4), (2, 6), (3, 7)]
+ mesh = obj.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+
+def create_root_widget(rig, bone_name):
+ """ Creates a widget for the root bone.
+ """
+ obj = create_widget(rig, bone_name)
+ if obj != None:
+ verts = [(0.7071067690849304, 0.7071067690849304, 0.0), (0.7071067690849304, -0.7071067690849304, 0.0), (-0.7071067690849304, 0.7071067690849304, 0.0), (-0.7071067690849304, -0.7071067690849304, 0.0), (0.8314696550369263, 0.5555701851844788, 0.0), (0.8314696550369263, -0.5555701851844788, 0.0), (-0.8314696550369263, 0.5555701851844788, 0.0), (-0.8314696550369263, -0.5555701851844788, 0.0), (0.9238795042037964, 0.3826834261417389, 0.0), (0.9238795042037964, -0.3826834261417389, 0.0), (-0.9238795042037964, 0.3826834261417389, 0.0), (-0.9238795042037964, -0.3826834261417389, 0.0), (0.9807852506637573, 0.19509035348892212, 0.0), (0.9807852506637573, -0.19509035348892212, 0.0), (-0.9807852506637573, 0.19509035348892212, 0.0), (-0.9807852506637573, -0.19509035348892212, 0.0), (0.19509197771549225, 0.9807849526405334, 0.0), (0.19509197771549225, -0.9807849526405334, 0.0), (-0.19509197771549225, 0.9807849526405334, 0.0), (-0.19509197771549225, -0.9807849526405334, 0.0), (0.3826850652694702, 0.9238788485527039, 0.0), (0.3826850652694702, -0.9238788485527039, 0.0), (-0.3826850652694702, 0.9238788485527039, 0.0), (-0.3826850652694702, -0.9238788485527039, 0.0), (0.5555717945098877, 0.8314685821533203, 0.0), (0.5555717945098877, -0.8314685821533203, 0.0), (-0.5555717945098877, 0.8314685821533203, 0.0), (-0.5555717945098877, -0.8314685821533203, 0.0), (0.19509197771549225, 1.2807848453521729, 0.0), (0.19509197771549225, -1.2807848453521729, 0.0), (-0.19509197771549225, 1.2807848453521729, 0.0), (-0.19509197771549225, -1.2807848453521729, 0.0), (1.280785322189331, 0.19509035348892212, 0.0), (1.280785322189331, -0.19509035348892212, 0.0), (-1.280785322189331, 0.19509035348892212, 0.0), (-1.280785322189331, -0.19509035348892212, 0.0), (0.3950919806957245, 1.2807848453521729, 0.0), (0.3950919806957245, -1.2807848453521729, 0.0), (-0.3950919806957245, 1.2807848453521729, 0.0), (-0.3950919806957245, -1.2807848453521729, 0.0), (1.280785322189331, 0.39509034156799316, 0.0), (1.280785322189331, -0.39509034156799316, 0.0), (-1.280785322189331, 0.39509034156799316, 0.0), (-1.280785322189331, -0.39509034156799316, 0.0), (0.0, 1.5807849168777466, 0.0), (0.0, -1.5807849168777466, 0.0), (1.5807852745056152, 0.0, 0.0), (-1.5807852745056152, 0.0, 0.0)]
+ edges = [(0, 4), (1, 5), (2, 6), (3, 7), (4, 8), (5, 9), (6, 10), (7, 11), (8, 12), (9, 13), (10, 14), (11, 15), (16, 20), (17, 21), (18, 22), (19, 23), (20, 24), (21, 25), (22, 26), (23, 27), (0, 24), (1, 25), (2, 26), (3, 27), (16, 28), (17, 29), (18, 30), (19, 31), (12, 32), (13, 33), (14, 34), (15, 35), (28, 36), (29, 37), (30, 38), (31, 39), (32, 40), (33, 41), (34, 42), (35, 43), (36, 44), (37, 45), (38, 44), (39, 45), (40, 46), (41, 46), (42, 47), (43, 47)]
+ mesh = obj.data
+ mesh.from_pydata(verts, edges, [])
+ mesh.update()
+
+
+#=============================================
+# Misc
+#=============================================
+
+
+def get_rig_type(rig_type):
+ """ Fetches a rig module by name, and returns it.
+ """
+ #print("%s.%s.%s" % (__package__,RIG_DIR,rig_type))
+ submod = __import__(name="%s.%s.%s" % (MODULE_NAME, RIG_DIR, rig_type), fromlist=[rig_type])
+ imp.reload(submod)
+ return submod
+
+
+def connected_children_names(obj, bone_name):
+ """ Returns a list of bone names (in order) of the bones that form a single
+ connected chain starting with the given bone as a parent.
+ If there is a connected branch, the list stops there.
+ """
+ bone = obj.data.bones[bone_name]
+ names = []
+
+ while True:
+ connects = 0
+ con_name = ""
+
+ for child in bone.children:
+ if child.use_connect:
+ connects += 1
+ con_name = child.name
+
+ if connects == 1:
+ names += [con_name]
+ bone = obj.data.bones[con_name]
+ else:
+ break
+
+ return names
+
+
+def get_layers(layers):
+ """ Does it's best to exctract a set of layers from any data thrown at it.
+ """
+ if type(layers) == int:
+ return [x == layers for x in range(0, 32)]
+ elif type(layers) == str:
+ s = layers.split(",")
+ l = []
+ for i in s:
+ try:
+ l += [int(float(i))]
+ except ValueError:
+ pass
+ return [x in l for x in range(0, 32)]
+ elif type(layers) == tuple or type(layers) == list:
+ return [x in layers for x in range(0, 32)]
+ else:
+ try:
+ list(layers)
+ except TypeError:
+ pass
+ else:
+ return [x in layers for x in range(0, 32)]
+
+
+def write_metarig(obj, layers=False, func_name="create_sample"):
+ '''
+ Write a metarig as a python script, this rig is to have all info needed for
+ generating the real rig with rigify.
+ '''
+ code = []
+
+ code.append("def %s(obj):" % func_name)
+ code.append(" # generated by rigify.utils.write_metarig")
+ bpy.ops.object.mode_set(mode='EDIT')
+ code.append(" bpy.ops.object.mode_set(mode='EDIT')")
+ code.append(" arm = obj.data")
+
+ arm = obj.data
+ # write parents first
+ bones = [(len(bone.parent_recursive), bone.name) for bone in arm.edit_bones]
+ bones.sort(key=lambda item: item[0])
+ bones = [item[1] for item in bones]
+
+ code.append("\n bones = {}\n")
+
+ for bone_name in bones:
+ bone = arm.edit_bones[bone_name]
+ code.append(" bone = arm.edit_bones.new('%s')" % bone.name)
+ code.append(" bone.head[:] = %.4f, %.4f, %.4f" % bone.head.to_tuple(4))
+ code.append(" bone.tail[:] = %.4f, %.4f, %.4f" % bone.tail.to_tuple(4))
+ code.append(" bone.roll = %.4f" % bone.roll)
+ code.append(" bone.use_connect = %s" % str(bone.use_connect))
+ if bone.parent:
+ code.append(" bone.parent = arm.edit_bones[bones['%s']]" % bone.parent.name)
+ code.append(" bones['%s'] = bone.name" % bone.name)
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ code.append("")
+ code.append(" bpy.ops.object.mode_set(mode='OBJECT')")
+
+ # Rig type and other pose properties
+ for bone_name in bones:
+ pbone = obj.pose.bones[bone_name]
+ pbone_written = False
+
+ code.append(" pbone = obj.pose.bones[bones['%s']]" % bone_name)
+ code.append(" pbone.rigify_type = '%s'" % pbone.rigify_type)
+ code.append(" pbone.lock_location = %s" % str(tuple(pbone.lock_location)))
+ code.append(" pbone.lock_rotation = %s" % str(tuple(pbone.lock_rotation)))
+ code.append(" pbone.lock_rotation_w = %s" % str(pbone.lock_rotation_w))
+ code.append(" pbone.lock_scale = %s" % str(tuple(pbone.lock_scale)))
+ code.append(" pbone.rotation_mode = '%s'" % str(pbone.rotation_mode))
+ if layers:
+ code.append(" pbone.bone.layers = %s" % str(list(pbone.bone.layers)))
+ # Rig type parameters
+ if len(pbone.rigify_parameters) > 0:
+ code.append(" pbone.rigify_parameters.add()")
+ for param_name in pbone.rigify_parameters[0].keys():
+ param = getattr(pbone.rigify_parameters[0], param_name)
+ if str(type(param)) == "<class 'bpy_prop_array'>":
+ param = list(param)
+ code.append(" try:")
+ code.append(" pbone.rigify_parameters[0].%s = %s" % (param_name, str(param)))
+ code.append(" except AttributeError:")
+ code.append(" pass")
+
+ code.append("\n bpy.ops.object.mode_set(mode='EDIT')")
+ code.append(" for bone in arm.edit_bones:")
+ code.append(" bone.select = False")
+ code.append(" bone.select_head = False")
+ code.append(" bone.select_tail = False")
+
+ code.append(" for b in bones:")
+ code.append(" bone = arm.edit_bones[bones[b]]")
+ code.append(" bone.select = True")
+ code.append(" bone.select_head = True")
+ code.append(" bone.select_tail = True")
+ code.append(" arm.edit_bones.active = bone")
+
+ return "\n".join(code)
+
+
+def random_string(length):
+ chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
+ text = ""
+ for i in range(0, length):
+ text += chars[randint(0, 35)]
+ return text
+
diff --git a/space_view3d_3d_navigation.py b/space_view3d_3d_navigation.py
new file mode 100644
index 00000000..d0162b43
--- /dev/null
+++ b/space_view3d_3d_navigation.py
@@ -0,0 +1,97 @@
+# 3D NAVIGATION TOOLBAR v1.2 - 3Dview Addon - Blender 2.5x
+#
+# THIS SCRIPT IS LICENSED UNDER GPL,
+# please read the license block.
+#
+# ##### 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": "3D Navigation",
+ "author": "Demohero, uriel",
+ "version": (1, 2),
+ "blender": (2, 5, 4),
+ "api": 32411,
+ "location": "View3D > Toolbar",
+ "description": "Navigate the Camera & 3d Views",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/3D_interaction/3D_Navigation",
+ "tracker_url": "http://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=23530&group_id=153&atid=468",
+ "category": "3D View"}
+
+# import the basic library
+import bpy
+
+# main class of this toolbar
+class VIEW3D_PT_3dnavigationPanel(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "3D Views "
+
+ def draw(self, context):
+ layout = self.layout
+ view = context.space_data
+
+# Triple boutons
+ col = layout.column(align=True)
+ row = col.row()
+ row.operator("view3d.viewnumpad", text="View Camera", icon='CAMERA_DATA').type='CAMERA'
+ row = col.row()
+ row.operator("view3d.localview", text="View Global/Local")
+ row = col.row()
+ row.operator("view3d.view_persportho", text="View Persp/Ortho")
+
+# group of 6 buttons
+ col = layout.column(align=True)
+ col.label(text="Align view from:")
+ row = col.row()
+ row.operator("view3d.viewnumpad", text="Front").type='FRONT'
+ row.operator("view3d.viewnumpad", text="Back").type='BACK'
+ row = col.row()
+ row.operator("view3d.viewnumpad", text="Left").type='LEFT'
+ row.operator("view3d.viewnumpad", text="Right").type='RIGHT'
+ row = col.row()
+ row.operator("view3d.viewnumpad", text="Top").type='TOP'
+ row.operator("view3d.viewnumpad", text="Bottom").type='BOTTOM'
+ row = col.row()
+
+# group of 2 buttons
+ col = layout.column(align=True)
+ col.label(text="View to Object:")
+ col.prop(view, "lock_object", text="")
+ row = col.row()
+ row.operator("view3d.view_selected", text="View to Selected")
+ col = layout.column(align=True)
+ col.label(text="Cursor:")
+ row = col.row()
+ row.operator("view3d.snap_cursor_to_center", text="Center")
+ row.operator("view3d.view_center_cursor", text="View")
+ row = col.row()
+ row.operator("view3d.snap_cursor_to_selected", text="Cursor to Selected")
+
+# register the class
+def register():
+ pass
+
+def unregister():
+ pass
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_align_tools.py b/space_view3d_align_tools.py
new file mode 100644
index 00000000..735c5b31
--- /dev/null
+++ b/space_view3d_align_tools.py
@@ -0,0 +1,338 @@
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_addon_info = {
+ "name": "Align Tools",
+ "author": "Gabriel Beaudin (gabhead)",
+ "version": (0,1),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "Tool Shelf",
+ "description": "Align selected objects to the active object",
+ "warning": "",
+ "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"}
+
+"""Align Selected Objects"""
+
+import bpy
+
+
+class AlignUi(bpy.types.Panel):
+ bl_space_type = 'VIEW_3D'
+ bl_region_type = 'TOOLS'
+
+ 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")
+
+
+##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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ ScaleZ(context)
+ return {'FINISHED'}
+
+## registring
+def register():
+ pass
+
+def unregister():
+ pass
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_copy_attributes.py b/space_view3d_copy_attributes.py
new file mode 100644
index 00000000..05b64ac1
--- /dev/null
+++ b/space_view3d_copy_attributes.py
@@ -0,0 +1,773 @@
+# ##### 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': 'Copy Attributes Menu',
+ 'author': 'Bassam Kurdali, Fabian Fricke, wiseman303',
+ 'version': (0, 41),
+ 'blender': (2, 5, 5),
+ 'api': 33215,
+ 'location': 'View3D > Ctrl/C',
+ 'description': 'Copy Attributes Menu from Blender 2.4',
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/Py/'\
+ 'Scripts/3D_interaction/Copy_Attributes_Menu',
+ 'tracker_url': 'https://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22588&group_id=153&atid=469',
+ 'category': '3D View'}
+
+__bpydoc__ = """
+Copy Menu
+
+
+"""
+import bpy
+import mathutils
+from mathutils import *
+
+
+def build_exec(loopfunc, func):
+ '''Generator function that returns exec functions for operators '''
+
+ def exec_func(self, context):
+ loopfunc(self, context, func)
+ return {'FINISHED'}
+ return exec_func
+
+
+def build_invoke(loopfunc, func):
+ '''Generator function that returns invoke functions for operators'''
+
+ def invoke_func(self, context, event):
+ loopfunc(self, context, func)
+ return {'FINISHED'}
+ return invoke_func
+
+
+def build_op(idname, label, description, fpoll, fexec, finvoke):
+ '''Generator function that returns the basic operator'''
+
+ class myopic(bpy.types.Operator):
+ bl_idname = idname
+ bl_label = label
+ bl_description = description
+ execute = fexec
+ poll = fpoll
+ invoke = finvoke
+ return myopic
+
+
+def genops(copylist, oplist, prefix, poll_func, loopfunc):
+ '''Generate ops from the copy list and its associated functions '''
+ for op in copylist:
+ exec_func = build_exec(loopfunc, op[3])
+ invoke_func = build_invoke(loopfunc, op[3])
+ opclass = build_op(prefix + op[0], "Copy " + op[1], op[2],
+ poll_func, exec_func, invoke_func)
+ oplist.append(opclass)
+
+
+def generic_copy(source, target, string=""):
+ ''' copy attributes from source to target that have string in them '''
+ for attr in dir(source):
+ if attr.find(string) > -1:
+ try:
+ setattr(target, attr, getattr(source, attr))
+ except:
+ pass
+ return
+
+
+def getmat(bone, active, context, ignoreparent):
+ '''Helper function for visual transform copy,
+ gets the active transform in bone space
+ '''
+ data_bone = context.active_object.data.bones[bone.name]
+ #all matrices are in armature space unless commented otherwise
+ otherloc = active.matrix # final 4x4 mat of target, location.
+ bonemat_local = Matrix(data_bone.matrix_local) # self rest matrix
+ if data_bone.parent:
+ parentposemat = Matrix(
+ context.active_object.pose.bones[data_bone.parent.name].matrix)
+ parentbonemat = Matrix(data_bone.parent.matrix_local)
+ else:
+ parentposemat = bonemat_local.copy().identity()
+ parentbonemat = bonemat_local.copy().identity()
+ if parentbonemat == parentposemat or ignoreparent:
+ newmat = bonemat_local.invert() * otherloc
+ else:
+ bonemat = parentbonemat.invert() * bonemat_local
+
+ newmat = bonemat.invert() * parentposemat.invert() * otherloc
+ return newmat
+
+
+def rotcopy(item, mat):
+ '''copy rotation to item from matrix mat depending on item.rotation_mode'''
+ if item.rotation_mode == 'QUATERNION':
+ item.rotation_quaternion = mat.rotation_part().to_quat()
+ elif item.rotation_mode == 'AXIS_ANGLE':
+ quat = mat.rotation_part().to_quat()
+ item.rotation_axis_angle = Vector([quat.axis[0],
+ quat.axis[1], quat.axis[2], quat.angle])
+ else:
+ item.rotation_euler = mat.rotation_part().to_euler(item.rotation_mode)
+
+
+def pLoopExec(self, context, funk):
+ '''Loop over selected bones and execute funk on them'''
+ active = context.active_pose_bone
+ selected = context.selected_pose_bones
+ selected.remove(active)
+ for bone in selected:
+ funk(bone, active, context)
+
+#The following functions are used o copy attributes frome active to bone
+
+
+def pLocLocExec(bone, active, context):
+ bone.location = active.location
+
+
+def pLocRotExec(bone, active, context):
+ rotcopy(bone, active.matrix_basis.rotation_part())
+
+
+def pLocScaExec(bone, active, context):
+ bone.scale = active.scale
+
+
+def pVisLocExec(bone, active, context):
+ bone.location = getmat(bone, active, context, False).translation_part()
+
+
+def pVisRotExec(bone, active, context):
+ rotcopy(bone, getmat(bone, active,
+ context, not context.active_object.data.bones[bone.name].use_inherit_rotation))
+
+
+def pVisScaExec(bone, active, context):
+ bone.scale = getmat(bone, active, context,
+ not context.active_object.data.bones[bone.name].use_inherit_scale)\
+ .scale_part()
+
+
+def pDrwExec(bone, active, context):
+ bone.custom_shape = active.custom_shape
+
+
+def pLokExec(bone, active, context):
+ for index, state in enumerate(active.lock_location):
+ bone.lock_location[index] = state
+ for index, state in enumerate(active.lock_rotation):
+ bone.lock_rotation[index] = state
+ bone.lock_rotations_4d = active.lock_rotations_4d
+ bone.lock_rotation_w = active.lock_rotation_w
+ for index, state in enumerate(active.lock_scale):
+ bone.lock_scale[index] = state
+
+
+def pConExec(bone, active, context):
+ for old_constraint in active.constraints.values():
+ new_constraint = bone.constraints.new(old_constraint.type)
+ generic_copy(old_constraint, new_constraint)
+
+
+def pIKsExec(bone, active, context):
+ generic_copy(active, bone, "ik_")
+
+pose_copies = (('POSE_LOC_LOC', "Local Location",
+ "Copy Location from Active to Selected", pLocLocExec),
+ ('POSE_LOC_ROT', "Local Rotation",
+ "Copy Rotation from Active to Selected", pLocRotExec),
+ ('POSE_LOC_SCA', "Local Scale",
+ "Copy Scale from Active to Selected", pLocScaExec),
+ ('POSE_VIS_LOC', "Visual Location",
+ "Copy Location from Active to Selected", pVisLocExec),
+ ('POSE_VIS_ROT', "Visual Rotation",
+ "Copy Rotation from Active to Selected", pVisRotExec),
+ ('POSE_VIS_SCA', "Visual Scale",
+ "Copy Scale from Active to Selected", pVisScaExec),
+ ('POSE_DRW', "Bone Shape",
+ "Copy Bone Shape from Active to Selected", pDrwExec),
+ ('POSE_LOK', "Protected Transform",
+ "Copy Protected Tranforms from Active to Selected", pLokExec),
+ ('POSE_CON', "Bone Constraints",
+ "Copy Object Constraints from Active to Selected", pConExec),
+ ('POSE_IKS', "IK Limits",
+ "Copy IK Limits from Active to Selected", pIKsExec))
+
+
+@classmethod
+def pose_poll_func(cls, context):
+ return(context.mode == 'POSE')
+
+
+def pose_invoke_func(self, context, event):
+ wm = context.window_manager
+ wm.invoke_props_dialog(self)
+ return {'RUNNING_MODAL'}
+
+
+class CopySelectedPoseConstraints(bpy.types.Operator):
+ ''' Copy Chosen constraints from active to selected'''
+ bl_idname = "pose.copy_selected_constraints"
+ bl_label = "Copy Selected Constraints"
+ selection = bpy.props.BoolVectorProperty(size=32)
+
+ poll = pose_poll_func
+ invoke = pose_invoke_func
+
+ def draw(self, context):
+ layout = self.layout
+ for idx, const in enumerate(context.active_pose_bone.constraints):
+ layout.prop(self, "selection", index=idx, text=const.name,
+ toggle=True)
+
+ def execute(self, context):
+ active = context.active_pose_bone
+ selected = context.selected_pose_bones[:]
+ selected.remove(active)
+ for bone in selected:
+ for index, flag in enumerate(self.selection):
+ if flag:
+ old_constraint = active.constraints[index]
+ new_constraint = bone.constraints.new(\
+ active.constraints[index].type)
+ generic_copy(old_constraint, new_constraint)
+ return {'FINISHED'}
+
+pose_ops = [] # list of pose mode copy operators
+
+genops(pose_copies, pose_ops, "pose.copy_", pose_poll_func, pLoopExec)
+
+
+class VIEW3D_MT_posecopypopup(bpy.types.Menu):
+ bl_label = "Copy Attributes"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ for op in pose_copies:
+ layout.operator("pose.copy_" + op[0])
+ layout.operator("pose.copy_selected_constraints")
+ layout.operator("pose.copy", text="copy pose")
+
+
+def obLoopExec(self, context, funk):
+ '''Loop over selected objects and execute funk on them'''
+ active = context.active_object
+ selected = context.selected_objects[:]
+ selected.remove(active)
+ for obj in selected:
+ msg = funk(obj, active, context)
+ if msg:
+ self.report({msg[0]}, msg[1])
+
+#The following functions are used o copy attributes from
+#active to selected object
+
+
+def obLoc(ob, active, context):
+ ob.location = active.location
+
+
+def obRot(ob, active, context):
+ rotcopy(ob, active.matrix_world.rotation_part())
+
+
+def obSca(ob, active, context):
+ ob.scale = active.scale
+
+
+def obDrw(ob, active, context):
+ ob.draw_type = active.draw_type
+ ob.show_axis = active.show_axis
+ ob.show_bounds = active.show_bounds
+ ob.draw_bounds_type = active.draw_bounds_type
+ ob.show_name = active.show_name
+ ob.show_texture_space = active.show_texture_space
+ ob.show_transparent = active.show_transparent
+ ob.show_wire = active.show_wire
+ ob.show_x_ray = active.show_x_ray
+ ob.empty_draw_type = active.empty_draw_type
+ ob.empty_draw_size = active.empty_draw_size
+
+
+def obOfs(ob, active, context):
+ ob.time_offset = active.time_offset
+ return('INFO', "time offset copied")
+
+
+def obDup(ob, active, context):
+ generic_copy(active, ob, "dupli")
+ return('INFO', "duplication method copied")
+
+
+def obCol(ob, active, context):
+ ob.color = active.color
+
+
+def obMas(ob, active, context):
+ ob.game.mass = active.game.mass
+ return('INFO', "mass copied")
+
+
+def obLok(ob, active, context):
+ for index, state in enumerate(active.lock_location):
+ ob.lock_location[index] = state
+ for index, state in enumerate(active.lock_rotation):
+ ob.lock_rotation[index] = state
+ for index, state in enumerate(active.lock_rotations_4d):
+ ob.lock_rotations_4d[index] = state
+ ob.lock_rotation_w = active.lock_rotation_w
+ for index, state in enumerate(active.lock_scale):
+ ob.lock_scale[index] = state
+ return('INFO', "transform locks copied")
+
+
+def obCon(ob, active, context):
+ #for consistency with 2.49, delete old constraints first
+ for removeconst in ob.constraints:
+ ob.constraints.remove(removeconst)
+ for old_constraint in active.constraints.values():
+ new_constraint = ob.constraints.new(old_constraint.type)
+ generic_copy(old_constraint, new_constraint)
+ return('INFO', "constraints copied")
+
+
+def obTex(ob, active, context):
+ if 'texspace_location' in dir(ob.data) and 'texspace_location' in dir(
+ active.data):
+ ob.data.texspace_location[:] = active.data.texspace_location[:]
+ if 'texspace_size' in dir(ob.data) and 'texspace_size' in dir(active.data):
+ ob.data.texspace_size[:] = active.data.texspace_size[:]
+ return('INFO', "texture space copied")
+
+
+def obIdx(ob, active, context):
+ ob.pass_index = active.pass_index
+ return('INFO', "pass index copied")
+
+
+def obMod(ob, active, context):
+ for modifier in ob.modifiers:
+ #remove existing before adding new:
+ ob.modifiers.remove(modifier)
+ for old_modifier in active.modifiers.values():
+ new_modifier = ob.modifiers.new(name=old_modifier.name,
+ type=old_modifier.type)
+ generic_copy(old_modifier, new_modifier)
+ return('INFO', "modifiers copied")
+
+
+def obWei(ob, active, context):
+ me_source = active.data
+ me_target = ob.data
+ # sanity check: do source and target have the same amount of verts?
+ if len(me_source.vertices) != len(me_target.vertices):
+ return('ERROR', "objects have different vertex counts, doing nothing")
+ vgroups_IndexName = {}
+ for i in range(0, len(active.vertex_groups)):
+ groups = active.vertex_groups[i]
+ vgroups_IndexName[groups.index] = groups.name
+ data = {} # vert_indices, [(vgroup_index, weights)]
+ for v in me_source.vertices:
+ vg = v.groups
+ vi = v.index
+ if len(vg) > 0:
+ vgroup_collect = []
+ for i in range(0, len(vg)):
+ vgroup_collect.append((vg[i].group, vg[i].weight))
+ data[vi] = vgroup_collect
+ # write data to target
+ if ob != active:
+ # add missing vertex groups
+ for vgroup_name in vgroups_IndexName.values():
+ #check if group already exists...
+ already_present = 0
+ for i in range(0, len(ob.vertex_groups)):
+ if ob.vertex_groups[i].name == vgroup_name:
+ already_present = 1
+ # ... if not, then add
+ if already_present == 0:
+ ob.vertex_groups.new(name=vgroup_name)
+ # write weights
+ for v in me_target.vertices:
+ for vi_source, vgroupIndex_weight in data.items():
+ if v.index == vi_source:
+
+ for i in range(0, len(vgroupIndex_weight)):
+ groupName = vgroups_IndexName[vgroupIndex_weight[i][0]]
+ groups = ob.vertex_groups
+ for vgs in range(0, len(groups)):
+ if groups[vgs].name == groupName:
+ ob.vertex_groups.assign(v.index, groups[vgs],
+ vgroupIndex_weight[i][1], "REPLACE")
+ return('INFO', "weights copied")
+
+object_copies = (('OBJ_LOC', "Location",
+ "Copy Location from Active to Selected", obLoc),
+ ('OBJ_ROT', "Rotation",
+ "Copy Rotation from Active to Selected", obRot),
+ ('OBJ_SCA', "Scale",
+ "Copy Scale from Active to Selected", obSca),
+ ('OBJ_DRW', "Draw Options",
+ "Copy Draw Options from Active to Selected", obDrw),
+ ('OBJ_OFS', "Time Offset",
+ "Copy Time Offset from Active to Selected", obOfs),
+ ('OBJ_DUP', "Dupli",
+ "Copy Dupli from Active to Selected", obDup),
+ ('OBJ_COL', "Object Color",
+ "Copy Object Color from Active to Selected", obCol),
+ ('OBJ_MAS', "Mass",
+ "Copy Mass from Active to Selected", obMas),
+ #('OBJ_DMP', "Damping",
+ #"Copy Damping from Active to Selected"),
+ #('OBJ_ALL', "All Physical Attributes",
+ #"Copy Physical Atributes from Active to Selected"),
+ #('OBJ_PRP', "Properties",
+ #"Copy Properties from Active to Selected"),
+ #('OBJ_LOG', "Logic Bricks",
+ #"Copy Logic Bricks from Active to Selected"),
+ ('OBJ_LOK', "Protected Transform",
+ "Copy Protected Tranforms from Active to Selected", obLok),
+ ('OBJ_CON', "Object Constraints",
+ "Copy Object Constraints from Active to Selected", obCon),
+ #('OBJ_NLA', "NLA Strips",
+ #"Copy NLA Strips from Active to Selected"),
+ #('OBJ_TEX', "Texture Space",
+ #"Copy Texture Space from Active to Selected", obTex),
+ #('OBJ_SUB', "Subsurf Settings",
+ #"Copy Subsurf Setings from Active to Selected"),
+ #('OBJ_SMO', "AutoSmooth",
+ #"Copy AutoSmooth from Active to Selected"),
+ ('OBJ_IDX', "Pass Index",
+ "Copy Pass Index from Active to Selected", obIdx),
+ ('OBJ_MOD', "Modifiers",
+ "Copy Modifiers from Active to Selected", obMod),
+ ('OBJ_WEI', "Vertex Weights",
+ "Copy vertex weights based on indices", obWei))
+
+
+@classmethod
+def object_poll_func(cls, context):
+ return(len(context.selected_objects) > 1)
+
+
+def object_invoke_func(self, context, event):
+ wm = context.window_manager
+ wm.invoke_props_dialog(self)
+ return {'RUNNING_MODAL'}
+
+
+class CopySelectedObjectConstraints(bpy.types.Operator):
+ ''' Copy Chosen constraints from active to selected'''
+ bl_idname = "object.copy_selected_constraints"
+ bl_label = "Copy Selected Constraints"
+ selection = bpy.props.BoolVectorProperty(size=32)
+
+ poll = object_poll_func
+
+ invoke = object_invoke_func
+
+ def draw(self, context):
+ layout = self.layout
+ for idx, const in enumerate(context.active_object.constraints):
+ layout.prop(self, "selection", index=idx, text=const.name,
+ toggle=True)
+
+ def execute(self, context):
+ active = context.active_object
+ selected = context.selected_objects[:]
+ selected.remove(active)
+ for obj in selected:
+ for index, flag in enumerate(self.selection):
+ if flag:
+ old_constraint = active.constraints[index]
+ new_constraint = obj.constraints.new(\
+ active.constraints[index].type)
+ generic_copy(old_constraint, new_constraint)
+ return{'FINISHED'}
+
+
+class CopySelectedObjectModifiers(bpy.types.Operator):
+ ''' Copy Chosen modifiers from active to selected'''
+ bl_idname = "object.copy_selected_modifiers"
+ bl_label = "Copy Selected Modifiers"
+ selection = bpy.props.BoolVectorProperty(size=32)
+
+ poll = object_poll_func
+
+ invoke = object_invoke_func
+
+ def draw(self, context):
+ layout = self.layout
+ for idx, const in enumerate(context.active_object.modifiers):
+ layout.prop(self, 'selection', index=idx, text=const.name,
+ toggle=True)
+
+ def execute(self, context):
+ active = context.active_object
+ selected = context.selected_objects[:]
+ selected.remove(active)
+ for obj in selected:
+ for index, flag in enumerate(self.selection):
+ if flag:
+ old_modifier = active.modifiers[index]
+ new_modifier = obj.modifiers.new(\
+ type=active.modifiers[index].type,
+ name=active.modifiers[index].name)
+ generic_copy(old_modifier, new_modifier)
+ return{'FINISHED'}
+
+object_ops = []
+genops(object_copies, object_ops, "object.copy_", object_poll_func, obLoopExec)
+
+
+class VIEW3D_MT_copypopup(bpy.types.Menu):
+ bl_label = "Copy Attributes"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ for op in object_copies:
+ layout.operator("object.copy_" + op[0])
+ layout.operator("object.copy_selected_constraints")
+ layout.operator("object.copy_selected_modifiers")
+
+#Begin Mesh copy settings:
+
+
+class MESH_MT_CopyFaceSettings(bpy.types.Menu):
+ bl_label = "Copy Face Settings"
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ def draw(self, context):
+ mesh = context.object.data
+ uv = len(mesh.uv_textures) > 1
+ vc = len(mesh.vertex_colors) > 1
+ layout = self.layout
+
+ layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
+ text="Copy Material")['mode'] = 'MAT'
+ if mesh.uv_textures.active:
+ layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
+ text="Copy Mode")['mode'] = 'MODE'
+ layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
+ text="Copy Transp")['mode'] = 'TRANSP'
+ layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
+ text="Copy Image")['mode'] = 'IMAGE'
+ layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
+ text="Copy UV Coords")['mode'] = 'UV'
+ if mesh.vertex_colors.active:
+ layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
+ text="Copy Vertex Colors")['mode'] = 'VCOL'
+ if uv or vc:
+ layout.separator()
+ if uv:
+ layout.menu("MESH_MT_CopyModeFromLayer")
+ layout.menu("MESH_MT_CopyTranspFromLayer")
+ layout.menu("MESH_MT_CopyImagesFromLayer")
+ layout.menu("MESH_MT_CopyUVCoordsFromLayer")
+ if vc:
+ layout.menu("MESH_MT_CopyVertexColorsFromLayer")
+
+
+def _buildmenu(self, mesh, mode):
+ layout = self.layout
+ if mode == 'VCOL':
+ layers = mesh.vertex_colors
+ else:
+ layers = mesh.uv_textures
+ for layer in layers:
+ if not layer.active:
+ op = layout.operator(MESH_OT_CopyFaceSettings.bl_idname,
+ text=layer.name)
+ op['layer'] = layer.name
+ op['mode'] = mode
+
+
+@classmethod
+def _poll_layer_uvs(cls, context):
+ return context.mode == "EDIT_MESH" and len(
+ context.object.data.uv_layers) > 1
+
+
+@classmethod
+def _poll_layer_vcols(cls, context):
+ return context.mode == "EDIT_MESH" and len(
+ context.object.data.vertex_colors) > 1
+
+
+def _build_draw(mode):
+ return (lambda self, context: _buildmenu(self, context.object.data, mode))
+
+_layer_menu_data = (("UV Coords", _build_draw("UV"), _poll_layer_uvs),
+ ("Images", _build_draw("IMAGE"), _poll_layer_uvs),
+ ("Mode", _build_draw("MODE"), _poll_layer_uvs),
+ ("Transp", _build_draw("TRANSP"), _poll_layer_uvs),
+ ("Vertex Colors", _build_draw("VCOL"), _poll_layer_vcols))
+_layer_menus = []
+for name, draw_func, poll_func in _layer_menu_data:
+ classname = "MESH_MT_Copy" + "".join(name.split()) + "FromLayer"
+ menuclass = type(classname, (bpy.types.Menu,),
+ dict(bl_label="Copy " + name + " from layer",
+ bl_idname=classname,
+ draw=draw_func,
+ poll=poll_func))
+ _layer_menus.append(menuclass)
+
+
+class MESH_OT_CopyFaceSettings(bpy.types.Operator):
+ """Copy settings from active face to all selected faces."""
+ bl_idname = 'mesh.copy_face_settings'
+ bl_label = "Copy Face Settings"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'EDIT_MESH'
+
+ def execute(self, context):
+ mesh = context.object.data
+ mode = getattr(self, 'mode', 'MODE')
+ layername = getattr(self, 'layer', None)
+
+ # Switching out of edit mode updates the selected state of faces and
+ # makes the data from the uv texture and vertex color layers available.
+ bpy.ops.object.editmode_toggle()
+
+ if mode == 'MAT':
+ from_data = mesh.faces
+ to_data = from_data
+ else:
+ if mode == 'VCOL':
+ layers = mesh.vertex_colors
+ act_layer = mesh.vertex_colors.active
+ else:
+ layers = mesh.uv_textures
+ act_layer = mesh.uv_textures.active
+ if not layers or (layername and not layername in layers):
+ return _end({'CANCELLED'})
+ from_data = layers[layername or act_layer.name].data
+ to_data = act_layer.data
+ from_face = from_data[mesh.faces.active]
+
+ for f in mesh.faces:
+ if f.select:
+ if to_data != from_data:
+ from_face = from_data[f.index]
+ if mode == 'MAT':
+ f.material_index = from_face.material_index
+ continue
+ to_face = to_data[f.index]
+ if to_face is from_face:
+ continue
+ if mode == 'VCOL':
+ to_face.color1 = from_face.color1
+ to_face.color2 = from_face.color2
+ to_face.color3 = from_face.color3
+ to_face.color4 = from_face.color4
+ elif mode == 'MODE':
+ to_face.use_alpha_sort = from_face.use_alpha_sort
+ to_face.use_billboard = from_face.use_billboard
+ to_face.use_collision = from_face.use_collision
+ to_face.use_halo = from_face.use_halo
+ to_face.hide = from_face.hide
+ to_face.use_light = from_face.use_light
+ to_face.use_object_color = from_face.use_object_color
+ to_face.use_shadow_cast = from_face.use_shadow_cast
+ to_face.use_blend_shared = from_face.use_blend_shared
+ to_face.use_image = from_face.use_image
+ to_face.use_bitmap_text = from_face.use_bitmap_text
+ to_face.use_twoside = from_face.use_twoside
+ elif mode == 'TRANSP':
+ to_face.blend_type = from_face.blend_type
+ elif mode in ('UV', 'IMAGE'):
+ attr = mode.lower()
+ setattr(to_face, attr, getattr(from_face, attr))
+ return _end({'FINISHED'})
+
+
+def _end(retval):
+ # Clean up by returning to edit mode like it was before.
+ bpy.ops.object.editmode_toggle()
+ return(retval)
+
+
+def _add_tface_buttons(self, context):
+ row = self.layout.row()
+ row.operator(MESH_OT_CopyFaceSettings.bl_idname,
+ text="Copy Mode")['mode'] = 'MODE'
+ row.operator(MESH_OT_CopyFaceSettings.bl_idname,
+ text="Copy Transp")['mode'] = 'TRANSP'
+
+
+def register():
+ ''' mostly to get the keymap working '''
+ kc = bpy.context.window_manager.keyconfigs['Blender']
+ km = kc.keymaps.get("Object Mode")
+ if km is None:
+ km = kc.keymaps.new(name="Object Mode")
+ kmi = km.items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
+ kmi.properties.name = 'VIEW3D_MT_copypopup'
+ km = kc.keymaps.get("Pose")
+ if km is None:
+ km = kc.keymaps.new(name="Pose")
+
+ kmi = km.items.get("pose.copy")
+ if kmi is not None:
+ kmi.idname = 'wm.call_menu'
+ else:
+ kmi = km.items.new('wm.call_menu', 'C', 'PRESS', ctrl=True)
+ kmi.properties.name = 'VIEW3D_MT_posecopypopup'
+ for menu in _layer_menus:
+ bpy.types.register(menu)
+ bpy.types.DATA_PT_texface.append(_add_tface_buttons)
+ km = kc.keymaps.get("Mesh")
+ if km is None:
+ km = kc.keymaps.new(name="Mesh")
+ kmi = km.items.new('wm.call_menu', 'C', 'PRESS')
+ kmi.ctrl = True
+ kmi.properties.name = 'MESH_MT_CopyFaceSettings'
+
+
+def unregister():
+ ''' mostly to remove the keymap '''
+ kms = bpy.context.window_manager.keyconfigs['Blender'].keymaps['Pose']
+ for item in kms.items:
+ if item.name == 'Call Menu' and item.idname == 'wm.call_menu' and \
+ item.properties.name == 'VIEW3D_MT_posecopypopup':
+ item.idname = 'pose.copy'
+ break
+ for menu in _layer_menus:
+ bpy.types.unregister(menu)
+ bpy.types.DATA_PT_texface.remove(_add_tface_buttons)
+ km = bpy.context.window_manager.keyconfigs.active.keymaps['Mesh']
+ for kmi in km.items:
+ if kmi.idname == 'wm.call_menu':
+ if kmi.properties.name == 'MESH_MT_CopyFaceSettings':
+ km.items.remove(kmi)
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_materials_utils.py b/space_view3d_materials_utils.py
new file mode 100644
index 00000000..16dd9489
--- /dev/null
+++ b/space_view3d_materials_utils.py
@@ -0,0 +1,689 @@
+#(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 #####
+
+bl_addon_info = {
+ "name": "Material Utils",
+ "author": "michaelw",
+ "version": (1,3),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "View3D > Q key",
+ "description": "Menu of material tools (assign, select by etc) in the 3D View",
+ "warning": "",
+ "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"}
+
+"""
+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
+"""
+
+
+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
+ i += 1
+ 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].use_image = True
+ else:
+ uvtex[f.index].use_image = 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
+
+ tex = bpy.data.textures.get(img.name)
+
+ if tex is None:
+ tex = bpy.data.textures.new(name=img.name, type='IMAGE')
+
+ 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:
+ mtex = mat.texture_slots.add()
+ mtex.texture = tex
+ mtex.texture_coords = 'UV'
+ mtex.use_map_color_diffuse = True
+
+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.uv_textures.active.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'}
+
+ @classmethod
+ def poll(cls, 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)
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ mn = self.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'}
+
+ @classmethod
+ def poll(cls, 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'}
+
+ @classmethod
+ def poll(cls, 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)
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ mn = self.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)
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ m1 = self.matorg
+ m2 = self.matrep
+ all = self.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
+
+
+def register():
+ km = bpy.context.window_manager.keyconfigs.default.keymaps['3D View']
+ kmi = km.items.new('wm.call_menu', 'Q', 'PRESS')
+ kmi.properties.name = "VIEW3D_MT_master_material"
+
+def unregister():
+ km = bpy.context.window_manager.keyconfigs.default.keymaps['3D View']
+ for kmi in km.items:
+ if kmi.idname == 'wm.call_menu':
+ if kmi.properties.name == "VIEW3D_MT_master_material":
+ km.items.remove(kmi)
+ break
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_math_vis/__init__.py b/space_view3d_math_vis/__init__.py
new file mode 100644
index 00000000..25f753fb
--- /dev/null
+++ b/space_view3d_math_vis/__init__.py
@@ -0,0 +1,99 @@
+#====================== 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": "Math Vis (Console)",
+ "author": "Campbell Barton",
+ "version": (0, 1),
+ "blender": (2, 5, 5),
+ "api": 33110,
+ "location": "3D View Toolbar, Python Console",
+ "description": "Display console defined mathutils variables in the 3D view",
+ "wiki_url": "",
+ "tracker_url": "",
+ "category": "3D View"}
+
+if "bpy" in locals():
+ import imp
+ imp.reload(utils)
+ imp.reload(draw)
+else:
+ from . import utils, draw
+
+import bpy
+
+
+class VIEW3D_PT_math_vis(bpy.types.Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_label = "Math View"
+
+ def draw(self, context):
+ layout = self.layout
+ view = context.space_data
+
+ col = layout.column(align=True)
+
+ callbacks = draw.callbacks
+ ok = False
+ for region in context.area.regions:
+ if callbacks.get(hash(region)):
+ ok = True
+ break
+
+ col.operator("view3d.math_vis_toggle", emboss=False, icon='CHECKBOX_HLT' if ok else 'CHECKBOX_DEHLT')
+
+
+
+class SetupMathView(bpy.types.Operator):
+ '''Draw a line with the mouse'''
+ bl_idname = "view3d.math_vis_toggle"
+ bl_label = "Use Math Vis"
+
+ def execute(self, context):
+ callbacks = draw.callbacks
+ region = context.region
+ region_id = hash(region)
+ cb_data = callbacks.get(region_id)
+ if cb_data is None:
+ handle_pixel = region.callback_add(draw.draw_callback_px, (self, context), 'POST_PIXEL')
+ handle_view = region.callback_add(draw.draw_callback_view, (self, context), 'POST_VIEW')
+ callbacks[region_id] = region, handle_pixel, handle_view
+ else:
+ region.callback_remove(cb_data[1])
+ region.callback_remove(cb_data[2])
+ del callbacks[region_id]
+
+ context.area.tag_redraw()
+ return {'FINISHED'}
+
+
+def console_hook():
+ for region, handle_pixel, handle_view in draw.callbacks.values():
+ region.tag_redraw()
+
+
+def register():
+ import console_python
+ console_python.execute.hooks.append((console_hook, ()))
+
+def unregister():
+ import console_python
+ console_python.execute.hooks.remove((console_hook, ()))
+
+ draw.callbacks_clear()
diff --git a/space_view3d_math_vis/draw.py b/space_view3d_math_vis/draw.py
new file mode 100644
index 00000000..a7f25d37
--- /dev/null
+++ b/space_view3d_math_vis/draw.py
@@ -0,0 +1,231 @@
+#====================== 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
+import blf
+
+from . import utils
+from mathutils import Vector, Matrix
+
+callbacks = {}
+
+def callbacks_clear():
+ for region, handle_pixel, handle_view in callbacks.values():
+ region.callback_remove(handle_pixel)
+ region.callback_remove(handle_view)
+ callbacks.clear()
+
+
+def draw_callback_px(self, context):
+ from bgl import glColor3f
+ font_id = 0 # XXX, need to find out how best to get this.
+ blf.size(font_id, 12, 72)
+
+ data_matrix, data_quat, data_euler, data_vector, data_vector_array = utils.console_math_data()
+
+ if not data_matrix and not data_quat and not data_euler and not data_vector and not data_vector_array:
+
+ # draw some text
+ glColor3f(1.0, 0.0, 0.0)
+ blf.position(font_id, 180, 10, 0)
+ blf.draw(font_id, "Python Console has no mathutils definitions")
+ return
+
+ glColor3f(1.0, 1.0, 1.0)
+
+ region = context.region
+ region3d = context.space_data.region_3d
+
+ region_mid_width = region.width / 2.0
+ region_mid_height = region.height / 2.0
+
+ # vars for projection
+ perspective_matrix = region3d.perspective_matrix.copy()
+
+ def draw_text(text, vec):
+ vec_4d = Vector((vec.x, vec.y, vec.z, 1.0))
+ vec_4d *= perspective_matrix
+ if vec_4d.w > 0.0:
+ x = region_mid_width + region_mid_width * (vec_4d.x / vec_4d.w)
+ y = region_mid_height + region_mid_height * (vec_4d.y / vec_4d.w)
+
+ blf.position(font_id, x + 3.0, y - 4.0, 0.0)
+ blf.draw(font_id, text)
+
+ # points
+ if data_vector:
+ for key, vec in data_vector.items():
+ draw_text(key, vec)
+
+ # lines
+ if data_vector_array:
+ for key, vec in data_vector_array.items():
+ draw_text(key, vec[0])
+
+ # matrix
+ if data_matrix:
+ for key, mat in data_matrix.items():
+ draw_text(key, mat[3])
+
+ if data_quat:
+ loc = context.scene.cursor_location.copy()
+ for key, mat in data_quat.items():
+ draw_text(key, loc)
+
+ if data_euler:
+ loc = context.scene.cursor_location.copy()
+ for key, mat in data_euler.items():
+ draw_text(key, loc)
+
+
+def draw_callback_view(self, context):
+ from bgl import glEnable, glDisable, glColor3f, glVertex3f, glPointSize, glLineWidth, glBegin, glEnd, glLineStipple, GL_POINTS, GL_LINE_STRIP, GL_LINES, GL_LINE_STIPPLE
+
+ data_matrix, data_quat, data_euler, data_vector, data_vector_array = utils.console_math_data()
+
+
+ # draw_matrix vars
+ zero = Vector((0.0, 0.0, 0.0))
+ x_p = Vector((1.0, 0.0, 0.0))
+ x_n = Vector((-1.0, 0.0, 0.0))
+ y_p = Vector((0.0, 1.0, 0.0))
+ y_n = Vector((0.0, -1.0, 0.0))
+ z_p = Vector((0.0, 0.0, 1.0))
+ z_n = Vector((0.0, 0.0, -1.0))
+ bb = [Vector() for i in range(8)]
+
+ def draw_matrix(mat):
+ zero_tx = zero * mat
+
+ glLineWidth(2.0)
+
+ # x
+ glColor3f(1.0, 0.2, 0.2)
+ glBegin(GL_LINES)
+ glVertex3f(*(zero_tx))
+ glVertex3f(*(x_p * mat))
+ glEnd()
+
+ glColor3f(0.6, 0.0, 0.0)
+ glBegin(GL_LINES)
+ glVertex3f(*(zero_tx))
+ glVertex3f(*(x_n * mat))
+ glEnd()
+
+ # y
+ glColor3f(0.2, 1.0, 0.2)
+ glBegin(GL_LINES)
+ glVertex3f(*(zero_tx))
+ glVertex3f(*(y_p * mat))
+ glEnd()
+
+ glColor3f(0.0, 0.6, 0.0)
+ glBegin(GL_LINES)
+ glVertex3f(*(zero_tx))
+ glVertex3f(*(y_n * mat))
+ glEnd()
+
+ # z
+ glColor3f(0.2, 0.2, 1.0)
+ glBegin(GL_LINES)
+ glVertex3f(*(zero_tx))
+ glVertex3f(*(z_p * mat))
+ glEnd()
+
+ glColor3f(0.0, 0.0, 0.6)
+ glBegin(GL_LINES)
+ glVertex3f(*(zero_tx))
+ glVertex3f(*(z_n * mat))
+ glEnd()
+
+ # bounding box
+ i = 0
+ glColor3f(1.0, 1.0, 1.0)
+ for x in (-1.0, 1.0):
+ for y in (-1.0, 1.0):
+ for z in (-1.0, 1.0):
+ bb[i][:] = x, y, z
+ bb[i] *= mat
+ i += 1
+
+ # strip
+ glLineWidth(1.0)
+ glLineStipple(1, 0xAAAA)
+ glEnable(GL_LINE_STIPPLE)
+
+ glBegin(GL_LINE_STRIP)
+ for i in 0, 1, 3, 2, 0, 4, 5, 7, 6, 4:
+ glVertex3f(*bb[i])
+ glEnd()
+
+ # not done by the strip
+ glBegin(GL_LINES)
+ glVertex3f(*bb[1])
+ glVertex3f(*bb[5])
+
+ glVertex3f(*bb[2])
+ glVertex3f(*bb[6])
+
+ glVertex3f(*bb[3])
+ glVertex3f(*bb[7])
+ glEnd()
+ glDisable(GL_LINE_STIPPLE)
+
+
+ # points
+ if data_vector:
+ glPointSize(3.0);
+ glBegin(GL_POINTS)
+ glColor3f(0.5, 0.5, 1)
+ for key, vec in data_vector.items():
+ glVertex3f(*vec.copy().resize3D())
+ glEnd();
+ glPointSize(1.0)
+
+ # lines
+ if data_vector_array:
+ glColor3f(0.5, 0.5, 1)
+ glLineWidth(2.0)
+
+ for line in data_vector_array.values():
+ glBegin(GL_LINE_STRIP)
+ for vec in line:
+ glVertex3f(*vec)
+ glEnd();
+ glPointSize(1.0)
+
+ glLineWidth(1.0)
+
+ # matrix
+ if data_matrix:
+ for mat in data_matrix.values():
+ draw_matrix(mat)
+
+ if data_quat:
+ loc = context.scene.cursor_location.copy()
+ for quat in data_quat.values():
+ mat = quat.to_matrix().resize4x4()
+ mat[3][0:3] = loc
+ draw_matrix(mat)
+
+ if data_euler:
+ loc = context.scene.cursor_location.copy()
+ for eul in data_euler.values():
+ mat = eul.to_matrix().resize4x4()
+ mat[3][0:3] = loc
+ draw_matrix(mat)
diff --git a/space_view3d_math_vis/utils.py b/space_view3d_math_vis/utils.py
new file mode 100644
index 00000000..a9c26ae4
--- /dev/null
+++ b/space_view3d_math_vis/utils.py
@@ -0,0 +1,64 @@
+#====================== 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 sys
+
+def console_namespace():
+ import console_python
+ get_consoles = console_python.get_console
+ consoles = getattr(get_consoles, "consoles", None)
+ if consoles:
+ for console, stdout, stderr in get_consoles.consoles.values():
+ return console.locals
+ return {}
+
+def console_math_data():
+ from mathutils import Matrix, Vector, Quaternion, Euler
+
+ data_matrix = {}
+ data_quat = {}
+ data_euler = {}
+ data_vector = {}
+ data_vector_array = {}
+
+ for key, var in console_namespace().items():
+ if key[0] == "_":
+ continue
+
+ var_type = type(var)
+
+ if var_type is Matrix:
+ data_matrix[key] = var
+ elif var_type is Vector:
+ data_vector[key] = var
+ elif var_type is Quaternion:
+ data_quat[key] = var
+ elif var_type is Euler:
+ data_euler[key] = var
+ elif var_type in (list, tuple):
+ if var:
+ ok = True
+ for item in var:
+ if type(item) is not Vector:
+ ok = False
+ break
+ if ok:
+ data_vector_array[key] = var
+
+ return data_matrix, data_quat, data_euler, data_vector, data_vector_array
+
diff --git a/space_view3d_panel_measure.py b/space_view3d_panel_measure.py
new file mode 100644
index 00000000..347776d4
--- /dev/null
+++ b/space_view3d_panel_measure.py
@@ -0,0 +1,1112 @@
+# ##### 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": "Measure Panel",
+ "author": "Buerbaum Martin (Pontiac)",
+ "version": (0, 7, 12),
+ "blender": (2, 5, 5),
+ "api": 33931,
+ "location": "View3D > Properties > Measure",
+ "description": "Measure distances between objects",
+ "warning": "",
+ "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"}
+
+"""
+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.12 - Moved setting of properties to callback function
+ (it is bad practise to set it in the draw code).
+ Fixed distance calculation of parented objects.
+ API change: add_modal_handler -> modal_handler_add
+ Regression: Had to disable area display for selection with
+ more than 2 meshes.
+ Fixed Local/Global vert-loc calculations in EditMode.
+v0.7.11 - Applied patch by Filiciss Muhgue that fixes the text in quad view.
+v0.7.10 - Applied patch by Filiciss Muhgue that (mostly) fixes the quad view.
+ Patch link: https://projects.blender.org/tracker/?func=
+ detail&atid=127&aid=24932&group_id=9
+ Thanks for that!
+ Removed (now) unneeded "attr" setting for properties.
+v0.7.9 - Updated scene properties for changes in property API.
+ See http://lists.blender.org/pipermail/bf-committers/
+ 2010-September/028654.html
+ Synced API changes in/from local copy.
+v0.7.8 - Various Py API changes by Campbell ...
+ bl_default_closed -> bl_options = {'DEFAULT_CLOSED'}
+ x.verts -> x.vertices
+ @classmethod def poll(cls, context)
+ No "location" in bl_addon_info->name
+ bl_addon_info->api
+v0.7.7 - One more change to the callback registration code.
+ Now it should finally work as intended.
+v0.7.6 - API changes (r885, r886) - register & unregister function
+v0.7.5.3 - Small fix for bug in v0.7.5.1
+ (location was off when object was moved)
+v0.7.5.2 - Changed callback registration back to original code &
+ fixed bug in there (use bl_idname instead of bl_label)
+v0.7.5.1 - Global mode is now taking rotation into account properly.
+v0.7.5 - Fixed lagging and drawing issues.
+v0.7.4 - Fixed the modal_handler_add and callback_add code.
+ Thanks to jesterKing for pointing that out :-)
+v0.7.3.1 - Fixed bug that made all lines in Blender stippled :-)
+v0.7.3 - Added display of delta x/y/z value in 3d view.
+ * Inspired by warpi's patch here:
+ http://blenderartists.org/forum/showpost.php?p=1671033&postcount=47
+ * Also added display of dx,dy,dz lines
+ * Changed the "dist" colors to something not already used
+ by x/y/z axes.
+v0.7.2 - Merged changes from trunk (scripts_addons r847):
+ * obj.matrix -> obj.matrix_world
+ * vert.selected -> vert.select
+ * face.selected -> face.select
+ * bl_addon_info: warning, wiki_url, tracker_url
+ * removed __bpydoc__
+ * Use fontid=0 for blf functions. 0 is the default font.
+v0.7.1 - Merged changes by Campbell:
+ * Fix for API change: Collections like context.selected_objects
+ no longer return None for empty lists.
+ * Update for mathutils, also stripped some redundant
+ conversions (Mostly "Vector()" stuff)
+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 necessarily 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.
+
+More links:
+http://gitorious.org/blender-scripts/blender-measure-panel-script
+http://blenderartists.org/forum/showthread.php?t=177800
+"""
+
+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.5, 0.0, 0.8)
+COLOR_GLOBAL = (0.5, 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 the selected vertices.
+ # @todo: Better (more efficient) way to do this?
+ verts_selected = [v for v in mesh.vertices 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.matrix_world.translation_part()
+
+ # 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()
+
+ # Convert to local or global space.
+ if measureLocal(sce):
+ p1 = vert_loc
+ p2 = cur_loc
+ return (p1, p2, COLOR_LOCAL)
+
+ else:
+ p1 = vert_loc * obj.matrix_world
+ 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.matrix_world.translation_part()
+ vert1_loc = verts_selected[0].co.copy()
+ vert2_loc = verts_selected[1].co.copy()
+
+ # Convert to local or global space.
+ if measureLocal(sce):
+ p1 = vert1_loc
+ p2 = vert2_loc
+ return (p1, p2, COLOR_LOCAL)
+
+ else:
+ p1 = vert1_loc * obj.matrix_world
+ p2 = vert2_loc * obj.matrix_world
+ return (p1, p2, COLOR_GLOBAL)
+
+ else:
+ return None
+
+ elif (context.mode == 'OBJECT'):
+ # We are working in 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.matrix_world.translation_part()
+ obj2_loc = obj2.matrix_world.translation_part()
+ 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.matrix_world.translation_part()
+ 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.vertices) == 4:
+ # Quad
+
+ # Get vertex indices
+ v1, v2, v3, v4 = face.vertices
+
+ # Get vertex data
+ v1 = obj.data.vertices[v1]
+ v2 = obj.data.vertices[v2]
+ v3 = obj.data.vertices[v3]
+ v4 = obj.data.vertices[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.vertices) == 3:
+ # Triangle
+
+ # Get vertex indices
+ v1, v2, v3 = face.vertices
+
+ # Get vertex data
+ v1 = obj.data.vertices[v1]
+ v2 = obj.data.vertices[v2]
+ v3 = obj.data.vertices[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.region_data.perspective_matrix
+ total_mat = view_mat
+
+ # Order is important
+ vec = Vector((loc_3d[0], loc_3d[1], loc_3d[2], 1.0)) * total_mat
+
+ # 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
+ region = view3d.region_data
+ 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]
+
+ line_stipple_prev = bgl.Buffer(bgl.GL_BYTE, [1])
+ bgl.glGetFloatv(bgl.GL_LINE_STIPPLE, line_stipple_prev)
+ line_stipple_prev = line_stipple_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.glEnable(bgl.GL_BLEND)
+ bgl.glEnable(bgl.GL_LINE_STIPPLE)
+
+ # ---
+ # Draw 3D stuff.
+ width = 1
+ bgl.glLineWidth(width)
+ # X
+ bgl.glColor4f(1, 0, 0, 0.8)
+ bgl.glBegin(bgl.GL_LINE_STRIP)
+ bgl.glVertex3f(p1[0], p1[1], p1[2])
+ bgl.glVertex3f(p2[0], p1[1], p1[2])
+ bgl.glEnd()
+ # Y
+ bgl.glColor4f(0, 1, 0, 0.8)
+ bgl.glBegin(bgl.GL_LINE_STRIP)
+ bgl.glVertex3f(p1[0], p1[1], p1[2])
+ bgl.glVertex3f(p1[0], p2[1], p1[2])
+ bgl.glEnd()
+ # Z
+ bgl.glColor4f(0, 0, 1, 0.8)
+ bgl.glBegin(bgl.GL_LINE_STRIP)
+ bgl.glVertex3f(p1[0], p1[1], p1[2])
+ bgl.glVertex3f(p1[0], p1[1], p2[2])
+ bgl.glEnd()
+
+ # Dist
+ width = 2
+ bgl.glLineWidth(width)
+ bgl.glColor4f(color[0], color[1], color[2], color[3])
+ 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)
+ if not line_stipple_prev:
+ bgl.glDisable(bgl.GL_LINE_STIPPLE)
+ 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_LINE = 10 # Offset the text a bit to the right.
+ OFFSET_Y = 15 # Offset of the lines.
+ OFFSET_VALUE = 30 # Offset of value(s) from the text.
+ dist = (p1 - p2).length
+
+ # Write distance value into the scene property,
+ # so we can display it in the panel & refresh the panel.
+ if hasattr(sce, "measure_panel_dist"):
+ sce.measure_panel_dist = dist
+ context.area.tag_redraw()
+
+ texts = [("Dist:", round(dist, PRECISION)),
+ ("X:", round(abs(p1[0] - p2[0]), PRECISION)),
+ ("Y:", round(abs(p1[1] - p2[1]), PRECISION)),
+ ("Z:", round(abs(p1[2] - p2[2]), PRECISION))]
+
+ # Draw all texts
+ # @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.
+
+ loc_x = coord_2d[0] + OFFSET_LINE
+ loc_y = coord_2d[1]
+ for t in texts:
+ text = t[0]
+ value = str(t[1]) + " BU"
+
+ blf.position(0, loc_x, loc_y, 0)
+ blf.draw(0, text)
+ blf.position(0, loc_x + OFFSET_VALUE, loc_y, 0)
+ blf.draw(0, value)
+
+ loc_y -= OFFSET_Y
+
+ # Handle mesh surface area calulations
+ if (sce.measure_panel_calc_area):
+ # 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):
+ # "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.vertices if v.select == 1]
+
+ if len(verts_selected) >= 3:
+ # 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):
+ sce.measure_panel_area1 = area
+
+ elif (context.mode == 'OBJECT'):
+ # We are working in object mode.
+
+ if len(context.selected_objects) > 2:
+ return
+# @todo Make this work again.
+# # We have more that 2 objects selected...
+#
+# 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.
+#
+# for o in mesh_objects:
+# area = objectSurfaceArea(o, False,
+# measureGlobal(sce))
+# if (area >= 0):
+# #row.label(text=o.name, icon='OBJECT_DATA')
+# #row.label(text=str(round(area, PRECISION))
+# # + " BU^2")
+
+ elif len(context.selected_objects) == 2:
+ # 2 objects selected.
+
+ obj1, obj2 = context.selected_objects
+
+ # Calculate surface area of the objects.
+ area1 = objectSurfaceArea(obj1, False, measureGlobal(sce))
+ area2 = objectSurfaceArea(obj2, False, measureGlobal(sce))
+ sce.measure_panel_area1 = area1
+ sce.measure_panel_area2 = area2
+
+ elif (obj):
+ # One object selected.
+
+ # Calculate surface area of the object.
+ area = objectSurfaceArea(obj, False, measureGlobal(sce))
+ if (area >= 0):
+ sce.measure_panel_area1 = area
+
+
+class VIEW3D_OT_display_measurements(bpy.types.Operator):
+ '''Display the measurements made in the 'Measure' panel'''
+ # Do not use bl_idname here (class name is used instead),
+ # so the callback can be added easily.
+ #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':
+ mgr_ops = context.window_manager.operators.values()
+ if not self.bl_idname in [op.bl_idname for op in mgr_ops]:
+ # Add the region OpenGL drawing callback
+ for WINregion in context.area.regions:
+ if WINregion.type == 'WINDOW':
+ context.window_manager.modal_handler_add(self)
+ self._handle = WINregion.callback_add(
+ draw_measurements_callback,
+ (self, context),
+ 'POST_PIXEL')
+
+ print("Measure panel display callback added")
+
+ return {'RUNNING_MODAL'}
+
+ return {'CANCELLED'}
+
+ 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_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, 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
+
+ # Force a redraw.
+ # This prevents the lines still be drawn after
+ # disabling the "measure_panel_draw" checkbox.
+ # @todo Better solution?
+ context.area.tag_redraw()
+
+ # Execute operator (this adds the callback)
+ # if it wasn't done yet.
+ bpy.ops.view3d.display_measurements()
+
+ # Define property for the draw setting.
+ bpy.types.Scene.measure_panel_draw = bpy.props.BoolProperty(
+ description="Draw distances in 3D View",
+ default=1)
+
+ # Define property for the calc-area setting.
+ # @todo prevent double calculations for each refresh automatically?
+ bpy.types.Scene.measure_panel_calc_area = bpy.props.BoolProperty(
+ 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
+
+ # Get a single selected object (or nothing).
+ obj = getSingleObject(context)
+
+ # Define a temporary attribute for the distance value
+ bpy.types.Scene.measure_panel_dist = bpy.props.FloatProperty(
+ name="Distance",
+ precision=PRECISION,
+ unit="LENGTH")
+ bpy.types.Scene.measure_panel_area1 = bpy.props.FloatProperty(
+ precision=PRECISION,
+ unit="AREA")
+ bpy.types.Scene.measure_panel_area2 = bpy.props.FloatProperty(
+ 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.measure_panel_transform = bpy.props.EnumProperty(
+ 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.vertices 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.
+
+ 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.
+
+ 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.
+
+ 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:
+ if (sce.measure_panel_area1 >= 0):
+ row = layout.row()
+ row.label(
+ text=str(len(faces_selected)),
+ icon='FACESEL')
+ 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 in 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()
+ row.label(text="Multiple objects not yet supported",
+ icon='INFO')
+ row = layout.row()
+ row.label(text="(= More than two meshes)",
+ icon='INFO')
+# @todo Make this work again.
+# 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
+
+ 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):
+ # Display surface area of the objects.
+ if (sce.measure_panel_area1 >= 0
+ or sce.measure_panel_area2 >= 0):
+ if (sce.measure_panel_area1 >= 0):
+ row = layout.row()
+ row.label(text=obj1.name, icon='OBJECT_DATA')
+ row.prop(sce, "measure_panel_area1")
+
+ if (sce.measure_panel_area2 >= 0):
+ row = layout.row()
+ row.label(text=obj2.name, icon='OBJECT_DATA')
+ 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.
+
+ 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='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):
+ # Display surface area of the object.
+
+ if (sce.measure_panel_area1 >= 0):
+ row = layout.row()
+ row.label(text=obj.name, icon='OBJECT_DATA')
+ 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.
+
+ 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():
+ pass
+
+
+def unregister():
+ pass
+
+if __name__ == "__main__":
+ register()
diff --git a/space_view3d_spacebar_menu.py b/space_view3d_spacebar_menu.py
new file mode 100644
index 00000000..f1c75739
--- /dev/null
+++ b/space_view3d_spacebar_menu.py
@@ -0,0 +1,1516 @@
+#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 #####
+
+bl_addon_info = {
+ "name": "Dynamic Spacebar Menu",
+ "author": "JayDez, sim88, meta-androcto, sam",
+ "version": (1, 6, 1),
+ "blender": (2, 5, 6),
+ "api": 34036,
+ "location": "View3D > Spacebar",
+ "description": "Context sensitive spacebar menu",
+ "warning": "",
+ "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"}
+
+"""
+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.6.1 - (JayDez) - Added Add Menu to Curve and Surface (respectively)
+v1.6 - (JayDez) - Fixed a couple wrong names. (Thanks Bao2 and Dennis)
+v1.5.1 - (JayDez) - Changing formatting to be more uniform.
+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
+"""
+
+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')
+
+ # Cursor Block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ 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()
+
+ # 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')
+ layout.separator()
+
+ # Add block
+ layout.menu("INFO_MT_mesh_add", text="Add Mesh",
+ icon='EDITMODE_HLT')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+
+ # 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()
+
+ # Multi Select
+ layout.menu("VIEW3D_MT_edit_multi", icon='VERTEXSEL')
+ 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_SelectEditMenu",
+ 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')
+ layout.separator()
+
+ # Add block
+ layout.menu("INFO_MT_curve_add", text="Add Curve",
+ icon='OUTLINER_OB_CURVE')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_edit",
+ icon="PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_edit_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()
+
+ # Select Curve Block
+ layout.menu("VIEW3D_MT_SelectCurveMenu",
+ 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 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')
+ layout.separator()
+
+ # Add block
+ layout.menu("INFO_MT_surface_add", text="Add Surface",
+ icon='OUTLINER_OB_SURFACE')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_edit",
+ icon="PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_edit_falloff",
+ icon="SMOOTHCURVE")
+ layout.separator()
+
+ # Edit Curve Specials
+ layout.menu("VIEW3D_MT_EditCurveSpecials",
+ icon='MODIFIER')
+ layout.separator()
+
+ # Select Surface
+ layout.menu("VIEW3D_MT_SelectSurface", 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 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')
+ layout.separator()
+
+ # Add block
+ #No INFO_MT_metaball_add find out why.. and how to use,
+ #for some reason also there is no add menu when editing mballs...
+ #layout.menu("INFO_MT_metaball_add", text="Add Metaball",
+ # icon='OUTLINER_OB_META')
+ #layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_edit",
+ icon="PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_edit_falloff",
+ icon="SMOOTHCURVE")
+ layout.separator()
+
+ #Select Metaball
+ layout.menu("VIEW3D_MT_SelectMetaball", 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 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')
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_edit",
+ icon= "PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_edit_falloff",
+ icon= "SMOOTHCURVE")
+ layout.separator()
+
+ layout.operator("lattice.make_regular")
+ layout.separator()
+
+ #Select Lattice
+ layout.menu("VIEW3D_MT_select_edit_lattice",
+ 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 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')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Proportional block
+ layout.prop_menu_enum(settings, "proportional_edit",
+ icon= "PROP_CON")
+ layout.prop_menu_enum(settings, "proportional_edit_falloff",
+ icon= "SMOOTHCURVE")
+ layout.separator()
+
+ # Particle block
+ layout.menu("VIEW3D_MT_particle", icon='PARTICLEMODE')
+ layout.separator()
+
+ #Select Particle
+ layout.menu("VIEW3D_MT_select_particle",
+ icon='RESTRICT_SELECT_OFF')
+
+ # 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')
+
+ # 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')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # 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')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # 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')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # 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')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+
+ # Cursor block
+ layout.menu("VIEW3D_MT_CursorMenu", icon='CURSOR')
+ layout.separator()
+
+ # Sculpt block
+ layout.menu("VIEW3D_MT_sculpt", icon='SCULPTMODE_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 == 'EDIT_ARMATURE':
+ # Armature menu
+
+ # Search Menu
+ layout.operator("wm.search_menu", text="Search", icon='VIEWZOOM')
+ layout.separator()
+
+ # Transform block
+ layout.menu("VIEW3D_MT_TransformMenu", icon='MANIPUL')
+
+ # Mirror block
+ layout.menu("VIEW3D_MT_MirrorMenu", icon='MOD_MIRROR')
+
+ # 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')
+ layout.separator()
+
+ # 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.draw_type in ('BBONE', 'ENVELOPE'):
+ layout.operator("transform.transform",
+ text="Scale Envelope Distance").mode = 'BONE_SIZE'
+
+ 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_select_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_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_all").action = 'TOGGLE'
+ layout.operator("mball.select_inverse_metaelems")
+ layout.operator("mball.select_random_metaelems")
+
+class VIEW3D_MT_edit_TK(bpy.types.Menu):
+ bl_label = "Edit Mesh Tools"
+
+ def draw(self, context):
+ layout = self.layout
+ row = layout.row()
+
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ layout.menu("VIEW3D_MT_edit_mesh_vertices", icon='VERTEXSEL')
+ layout.menu("VIEW3D_MT_edit_mesh_edges", icon='EDGESEL')
+ 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_edit_multi(bpy.types.Menu):
+ bl_label = "Multi Select"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+
+ layout.separator()
+ prop = layout.operator("wm.context_set_value", text="Vertex Select",
+ icon='VERTEXSEL')
+ prop.value = "(True, False, False)"
+ prop.data_path = "tool_settings.mesh_select_mode"
+
+ prop = layout.operator("wm.context_set_value", text="Edge Select",
+ icon='EDGESEL')
+ prop.value = "(False, True, False)"
+ prop.data_path = "tool_settings.mesh_select_mode"
+
+ prop = layout.operator("wm.context_set_value", text="Face Select",
+ icon='FACESEL')
+ prop.value = "(False, False, True)"
+ prop.data_path = "tool_settings.mesh_select_mode"
+ layout.separator()
+
+ prop = layout.operator("wm.context_set_value",
+ text="Vertex & Edge Select", icon='EDITMODE_HLT')
+ prop.value = "(True, True, False)"
+ prop.data_path = "tool_settings.mesh_select_mode"
+
+ prop = layout.operator("wm.context_set_value",
+ text="Vertex & Face Select", icon='ORTHO')
+ prop.value = "(True, False, True)"
+ prop.data_path = "tool_settings.mesh_select_mode"
+
+ prop = layout.operator("wm.context_set_value",
+ text="Edge & Face Select", icon='SNAP_FACE')
+ prop.value = "(False, True, True)"
+ prop.data_path = "tool_settings.mesh_select_mode"
+ layout.separator()
+
+ prop = layout.operator("wm.context_set_value",
+ text="Vertex & Edge & Face Select", icon='SNAP_VOLUME')
+ prop.value = "(True, True, True)"
+ prop.data_path = "tool_settings.mesh_select_mode"
+
+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 = 'BONE_SIZE'
+
+ layout.operator("transform.transform",
+ text="Scale B-Bone Width").mode = 'BONE_SIZE'
+ 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", 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"
+
+ @classmethod
+ def poll(cls, 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"
+
+ @classmethod
+ def poll(cls, 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.vertices
+
+ 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].vertices[0]].co,
+ verts[edges[0].vertices[1]].co, verts[edges[1].vertices[0]].co,
+ verts[edges[1].vertices[1]].co)
+
+ if (line is None):
+ operator.report({'ERROR'}, "Selected edges do not intersect.")
+ return
+
+ point = ((line[0] + line[1]) / 2)
+
+ 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"
+
+ @classmethod
+ def poll(cls, 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():
+ km = bpy.context.window_manager.keyconfigs.default.keymaps['3D View']
+ kmi = km.items.new('wm.call_menu', 'SPACE', 'PRESS')
+ kmi.properties.name = "VIEW3D_MT_Space_Dynamic_Menu"
+
+
+def unregister():
+ km = bpy.context.window_manager.keyconfigs.default.keymaps['3D View']
+ for kmi in km.items:
+ if kmi.idname == 'wm.call_menu':
+ if kmi.properties.name == "VIEW3D_MT_Space_Dynamic_Menu":
+ km.items.remove(kmi)
+ break
+
+if __name__ == "__main__":
+ register()
diff --git a/system_blend_info.py b/system_blend_info.py
new file mode 100644
index 00000000..7c532a1c
--- /dev/null
+++ b/system_blend_info.py
@@ -0,0 +1,207 @@
+# scene_blend_info.py Copyright (C) 2010, Mariano Hidalgo
+#
+# Show Information About the Blend.
+# ***** 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": "Scene Information",
+ "author": "uselessdreamer",
+ "version": (0,3),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "Properties space > Scene tab > Blend Info panel",
+ "description": "Show information about the .blend",
+ "warning": "",
+ "wiki_url": 'http://wiki.blender.org/index.php/Extensions:2.5/Py/' \
+ 'Scripts/System/Blend Info',
+ "tracker_url": "https://projects.blender.org/tracker/index.php?" \
+ "func=detail&aid=22102&group_id=153&atid=469",
+ "category": "System"}
+
+import bpy
+
+
+def quantity_string(quantity, text_single, text_plural, text_none=None):
+ sep = " "
+
+ if not text_none:
+ text_none = text_plural
+
+ if quantity == 0:
+ string = str(quantity) + sep + text_none
+
+ if quantity == 1:
+ string = str(quantity) + sep + text_single
+
+ if quantity >= 2:
+ string = str(quantity) + sep + text_plural
+
+ if quantity < 0:
+ return None
+
+ return string
+
+
+class OBJECT_PT_blendinfo(bpy.types.Panel):
+ bl_label = "Blend Info"
+ bl_space_type = "PROPERTIES"
+ bl_region_type = "WINDOW"
+ bl_context = "scene"
+
+ def draw(self, context):
+ amount = 2
+ ob_cols = []
+ db_cols = []
+ etc_cols = []
+
+ objects = bpy.data.objects
+
+ layout = self.layout
+
+ # OBJECTS
+
+ l_row = layout.row()
+ num = len(bpy.data.objects)
+ l_row.label(text=quantity_string(num, "Object", "Objects")
+ + " in the scene:",
+ icon='OBJECT_DATA')
+
+ l_row = layout.row()
+ ob_cols.append(l_row.column())
+ ob_cols.append(l_row.column())
+
+ row = ob_cols[0].row()
+ meshes = [o for o in objects.values() if o.type == 'MESH']
+ num = len(meshes)
+ row.label(text=quantity_string(num, "Mesh", "Meshes"),
+ icon='MESH_DATA')
+
+ row = ob_cols[1].row()
+ curves = [o for o in objects.values() if o.type == 'CURVE']
+ num = len(curves)
+ row.label(text=quantity_string(num, "Curve", "Curves"),
+ icon='CURVE_DATA')
+
+ row = ob_cols[0].row()
+ cameras = [o for o in objects.values() if o.type == 'CAMERA']
+ num = len(cameras)
+ row.label(text=quantity_string(num, "Camera", "Cameras"),
+ icon='CAMERA_DATA')
+
+ row = ob_cols[1].row()
+ lamps = [o for o in objects.values() if o.type == 'LAMP']
+ num = len(lamps)
+ row.label(text=quantity_string(num, "Lamp", "Lamps"),
+ icon='LAMP_DATA')
+
+ row = ob_cols[0].row()
+ armatures = [o for o in objects.values() if o.type == 'ARMATURE']
+ num = len(armatures)
+ row.label(text=quantity_string(num, "Armature", "Armatures"),
+ icon='ARMATURE_DATA')
+
+ row = ob_cols[1].row()
+ lattices = [o for o in objects.values() if o.type == 'LATTICE']
+ num = len(lattices)
+ row.label(text=quantity_string(num, "Lattice", "Lattices"),
+ icon='LATTICE_DATA')
+
+ row = ob_cols[0].row()
+ empties = [o for o in objects.values() if o.type == 'EMPTY']
+ num = len(empties)
+ row.label(text=quantity_string(num, "Empty", "Empties"),
+ icon='EMPTY_DATA')
+
+ l_row_sep = layout.separator()
+
+ # DATABLOCKS
+
+ l_row = layout.row()
+ num = len(bpy.data.objects)
+ l_row.label(text="Datablocks in the scene:")
+
+ l_row = layout.row()
+ db_cols.append(l_row.column())
+ db_cols.append(l_row.column())
+
+ row = db_cols[0].row()
+ num = len(bpy.data.meshes)
+ row.label(text=quantity_string(num, "Mesh", "Meshes"),
+ icon='MESH_DATA')
+
+ row = db_cols[1].row()
+ num = len(bpy.data.curves)
+ row.label(text=quantity_string(num, "Curve", "Curves"),
+ icon='CURVE_DATA')
+
+ row = db_cols[0].row()
+ num = len(bpy.data.cameras)
+ row.label(text=quantity_string(num, "Camera", "Cameras"),
+ icon='CAMERA_DATA')
+
+ row = db_cols[1].row()
+ num = len(bpy.data.lamps)
+ row.label(text=quantity_string(num, "Lamp", "Lamps"),
+ icon='LAMP_DATA')
+
+ row = db_cols[0].row()
+ num = len(bpy.data.armatures)
+ row.label(text=quantity_string(num, "Armature", "Armatures"),
+ icon='ARMATURE_DATA')
+
+ row = db_cols[1].row()
+ num = len(bpy.data.lattices)
+ row.label(text=quantity_string(num, "Lattice", "Lattices"),
+ icon='LATTICE_DATA')
+
+ row = db_cols[0].row()
+ num = len(bpy.data.materials)
+ row.label(text=quantity_string(num, "Material", "Materials"),
+ icon='MATERIAL_DATA')
+
+ row = db_cols[1].row()
+ num = len(bpy.data.worlds)
+ row.label(text=quantity_string(num, "World", "Worlds"),
+ icon='WORLD_DATA')
+
+ row = db_cols[0].row()
+ num = len(bpy.data.textures)
+ row.label(text=quantity_string(num, "Texture", "Textures"),
+ icon='TEXTURE_DATA')
+
+ row = db_cols[1].row()
+ num = len(bpy.data.images)
+ row.label(text=quantity_string(num, "Image", "Images"),
+ icon='IMAGE_DATA')
+
+ row = db_cols[0].row()
+ num = len(bpy.data.texts)
+ row.label(text=quantity_string(num, "Text", "Texts"),
+ icon='TEXT')
+
+
+def register():
+ pass
+
+def unregister():
+ pass
+
+if __name__ == "__main__":
+ register()
diff --git a/system_icon_get.py b/system_icon_get.py
new file mode 100644
index 00000000..5711690d
--- /dev/null
+++ b/system_icon_get.py
@@ -0,0 +1,171 @@
+# ##### 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': 'Icons',
+ 'author': 'Crouch, N.tox, PKHG, Campbell Barton, Dany Lebel',
+ 'version': (1, 4, 2),
+ 'blender': (2, 5, 6),
+ 'api': 33928,
+ 'location': 'Text window > Properties panel (ctrl+F) or '\
+ 'Console > Console menu',
+ 'warning': '',
+ 'description': 'Click an icon to display its name and copy it '\
+ 'to the clipboard',
+ 'wiki_url': 'http://wiki.blender.org/index.php/Extensions:2.5/'\
+ 'Py/Scripts/System/Display_All_Icons',
+ 'tracker_url': 'http://projects.blender.org/tracker/index.php?'\
+ 'func=detail&aid=22011&group_id=153&atid=469',
+ 'category': 'System'}
+
+
+import bpy
+
+
+def create_icon_list():
+ list = bpy.types.UILayout.bl_rna.functions['prop'].\
+ parameters['icon'].items.keys()
+ list.remove("BLENDER")
+ return list
+
+
+class IconProps(bpy.types.IDPropertyGroup):
+ """
+ Fake module like class
+ bpy.context.scene.icon_props
+ """
+ pass
+
+
+class WM_OT_icon_info(bpy.types.Operator):
+ bl_label = "Icon Info"
+ bl_description = "Click to copy this icon name to the clipboard"
+ icon = bpy.props.StringProperty()
+ icon_scroll = bpy.props.IntProperty()
+
+ def invoke(self, context, event):
+ bpy.data.window_managers['WinMan'].clipboard = self.icon
+ self.report({'INFO'}, "Icon ID: %s" % self.icon)
+ return {'FINISHED'}
+
+
+class OBJECT_PT_icons(bpy.types.Panel):
+ bl_space_type = "TEXT_EDITOR"
+ bl_region_type = "UI"
+ bl_label = "All icons"
+
+ def __init__(self, x):
+ self.amount = 10
+ self.icon_list = create_icon_list()
+
+ def draw(self, context):
+ box = self.layout.box()
+
+ # scroll view
+ if not context.scene.icon_props.expand:
+ row = box.row()
+ row.prop(context.scene.icon_props, "expand",
+ icon="TRIA_RIGHT", icon_only=True, emboss=False)
+ row.prop(context.scene.icon_props, "scroll")
+
+ row = box.row(align=True)
+ for icon in self.icon_list[context.scene.icon_props.scroll-1:
+ context.scene.icon_props.scroll-1+self.amount]:
+ row.operator("wm.icon_info", text=" ", icon=icon,
+ emboss=False).icon = icon
+
+ # expanded view
+ else:
+ row = box.row()
+ row.prop(context.scene.icon_props, "expand",
+ icon="TRIA_DOWN", icon_only=True, emboss=False)
+ row = row.row()
+ row.active = False
+ row.prop(context.scene.icon_props, "scroll")
+
+ col = box.column(align=True)
+ row = col.row(align=True)
+ for i, icon in enumerate(self.icon_list):
+ if i % self.amount == 0:
+ row = col.row(align=True)
+ row.operator("wm.icon_info", text=" ", icon=icon,
+ emboss=False).icon = icon
+
+
+class CONSOLE_HT_icons(bpy.types.Header):
+ bl_space_type = 'CONSOLE'
+
+ def __init__(self, x):
+ self.amount = 10
+ self.icon_list = create_icon_list()
+
+ def draw(self, context):
+ # scroll view
+ if context.scene.icon_props.console:
+ layout = self.layout
+ layout.separator()
+ row = layout.row()
+ row.prop(context.scene.icon_props, "scroll")
+ row = layout.row(align=True)
+
+ for icon in self.icon_list[context.scene.icon_props.scroll-1:
+ context.scene.icon_props.scroll-1+self.amount]:
+ row.operator("wm.icon_info", text="", icon=icon,
+ emboss=False).icon = icon
+
+
+def menu_func(self, context):
+ self.layout.prop(bpy.context.scene.icon_props, 'console')
+
+
+def register():
+ icons_total = len(create_icon_list())
+ icons_per_row = 10
+
+ bpy.types.Scene.icon_props = bpy.props.PointerProperty(type = IconProps)
+ IconProps.console = bpy.props.BoolProperty(
+ name='Show System Icons',
+ description='Display the Icons in the console header', default=False)
+ IconProps.expand = bpy.props.BoolProperty(default=False,
+ description="Expand, to display all icons at once")
+ IconProps.scroll = bpy.props.IntProperty(default=1, min=1,
+ max=icons_total - icons_per_row + 1,
+ description="Drag to scroll icons")
+
+ bpy.types.CONSOLE_MT_console.append(menu_func)
+
+
+def unregister():
+ if bpy.context.scene.get('icon_props') != None:
+ del bpy.context.scene['icon_props']
+ try:
+ del bpy.types.Scene.icon_props
+ bpy.types.CONSOLE_MT_console.remove(menu_func)
+ except:
+ pass
+ if __name__ == "__main__":
+ # unregistering is only done automatically when run as add-on
+ bpy.types.unregister(OBJECT_PT_icons)
+ bpy.types.unregister(CONSOLE_HT_icons)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/system_property_chart.py b/system_property_chart.py
new file mode 100644
index 00000000..d0705654
--- /dev/null
+++ b/system_property_chart.py
@@ -0,0 +1,230 @@
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_addon_info = {
+ "name": "Object Property Chart",
+ "author": "Campbell Barton (ideasman42)",
+ "version": (0, 1),
+ "blender": (2, 5, 3),
+ "api": 32411,
+ "location": "Tool Shelf",
+ "description": "Edit arbitrary selected properties for objects of the same type",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/System/Object Property Chart",
+ "tracker_url": "https://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=22701&group_id=153&atid=469",
+ "category": "System"}
+
+"""List properties of selected objects"""
+
+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
+
+ @classmethod
+ def poll(cls, 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.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.data_path_active
+ self.context_data_path_selected = self.data_path_selected
+
+ _property_chart_copy(self, context)
+
+ return {'FINISHED'}
+
+
+def register():
+ pass
+
+
+def unregister():
+ pass
+
+if __name__ == "__main__":
+ register()
diff --git a/text_editor_api_navigator.py b/text_editor_api_navigator.py
new file mode 100644
index 00000000..a92e5985
--- /dev/null
+++ b/text_editor_api_navigator.py
@@ -0,0 +1,725 @@
+# text_editor_api_navigator.py
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_addon_info = {
+ "name": "API Navigator",
+ "author": "Dany Lebel (Axon_D)",
+ "version": (1, 0),
+ "blender": (2, 5, 5),
+ "api": 32760,
+ "location": "Text Editor -> Properties (CTRL+F) -> API Navigator Panel",
+ "description": "Allows to explore the python api via the user interface",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"
+ "Scripts/Text_Editor/API_Navigator",
+ "tracker_url": "http://projects.blender.org/tracker/index.php?"\
+ "func=detail&aid=24982&group_id=153&atid=469",
+ "category": "Text Editor"}
+
+"""
+ You can browse through the tree structure of the api. Each child object appears in a list
+that tries to be representative of its type. These lists are :
+
+ * Items (for an iterable object)
+ * Item Values (for an iterable object wich only supports index)
+ * Modules
+ * Types
+ * Properties
+ * Structs and Functions
+ * Methods and Functions
+ * Attributes
+ * Inaccessible (some objects may be listed but inaccessible)
+
+ The lists can be filtered to help searching in the tree. Just enter the text in the
+filter section. It is also possible to explore other modules. Go the the root and select
+it in the list of available modules. It will be imported dynamically.
+
+ In the text section, some informations are displayed. The type of the object,
+what it returns, and its docstring. We could hope that these docstrings will be as
+descriptive as possible. This text data block named api_doc_ can be toggled on and off
+with the Escape key. (but a bug prevent the keymap to register correctly at start)
+
+"""
+
+import bpy, sys
+from console.complete_import import get_root_modules
+
+
+############ Global Variables ############
+
+last_text = None # last text data block
+
+root_module = None # root module of the tree
+
+root_m_path = '' # root_module + path as a string
+
+current_module = None # the object itself in the tree structure
+
+
+tree_level = None # the list of objects from the current_module
+
+def init_tree_level():
+ global tree_level
+ tree_level = [[],[],[],[],[],[],[], [], []]
+
+init_tree_level()
+
+
+api_doc_ = '' # the documentation formated for the API Navigator
+
+module_type = None # the type of current_module
+
+return_report = '' # what current_module returns
+
+filter_mem = {} # remember last filters entered for each path
+
+too_long = False # is tree_level list too long to display in a panel?
+
+
+############ Functions ############
+
+def get_root_module(path):
+ #print('get_root_module')
+ global root_module
+ if '.' in path:
+ root = path[:path.find('.')]
+ else :
+ root = path
+ try :
+ root_module = __import__(root)
+ except :
+ root_module = None
+
+
+def evaluate(module):
+ #print('evaluate')
+ global root_module, tree_level, root_m_path
+
+ path = bpy.context.window_manager.api_nav_props.path
+ try :
+ len_name = root_module.__name__.__len__()
+ root_m_path = 'root_module' + module[len_name:]
+ current_module = eval(root_m_path)
+ return current_module
+ except :
+ init_tree_level
+ return None
+
+
+def get_tree_level():
+ #print('get_tree_level')
+
+ path = bpy.context.window_manager.api_nav_props.path
+
+ def object_list():
+ #print('object_list')
+ global current_module, root_m_path
+
+ itm, val, mod, typ, props, struct, met, att, bug = [], [], [], [], [], [], [], [], []
+ iterable = isiterable(current_module)
+ if iterable:
+ iter(current_module)
+ current_type = str(module_type)
+ if current_type != "<class 'str'>":
+ if iterable == 'a':
+ #if iterable == 'a':
+ #current_type.__iter__()
+ itm = list(current_module.keys())
+ if not itm:
+ val = list(current_module)
+ else :
+ val = list(current_module)
+
+ for i in dir(current_module):
+ try :
+ t = str(type(eval(root_m_path + '.' + i)))
+ except AttributeError :
+ bug += [i]
+ continue
+
+ if t == "<class 'module'>":
+ mod += [i]
+ elif t[0:16] == "<class 'bpy_prop":
+ props += [i]
+ elif t[8:11] == 'bpy':
+ struct += [i]
+ elif t == "<class 'builtin_function_or_method'>":
+ met += [i]
+ elif t == "<class 'type'>":
+ typ += [i]
+ else :
+ att += [i]
+
+ return [itm, val, mod, typ, props, struct, met, att, bug]
+
+ if not path:
+ return [[], [], [i for i in get_root_modules()], [], [], [], [], [], []]
+ return object_list()
+
+
+def parent(path):
+ """Returns the parent path"""
+ #print('parent')
+
+ parent = path
+ if parent[-1] == ']' and '[' in parent:
+ while parent[-1] != '[':
+ parent = parent[:-1]
+ elif '.' in parent:
+ while parent[-1] != '.':
+ parent = parent[:-1]
+ else :
+ return ''
+ parent = parent[:-1]
+ return parent
+
+
+def update_filter(self):
+ """Update the filter according to the current path"""
+ global filter_mem
+
+ try :
+ bpy.context.window_manager.api_nav_props.filter = filter_mem[bpy.context.window_manager.api_nav_props.path]
+ except :
+ bpy.context.window_manager.api_nav_props.filter = ''
+ return {'FINISHED'}
+
+
+def isiterable(mod):
+
+ try :
+ iter(mod)
+ except :
+ return False
+ try :
+ mod['']
+ return 'a'
+ except KeyError:
+ return 'a'
+ except (AttributeError, TypeError):
+ return 'b'
+
+
+def fill_filter_mem():
+ global filter_mem
+
+ filter = bpy.context.window_manager.api_nav_props.filter
+ if filter:
+ filter_mem[bpy.context.window_manager.api_nav_props.old_path] = bpy.context.window_manager.api_nav_props.filter
+ else :
+ filter_mem.pop(bpy.context.window_manager.api_nav_props.old_path, None)
+
+
+############ Api Tree Properties ############
+
+def addProperties(ApiNavProps):
+ from bpy.props import StringProperty, PointerProperty, IntProperty, BoolProperty
+
+ bpy.types.WindowManager.api_nav_props = PointerProperty(
+ type=ApiNavProps, name='API Nav Props', description='')
+
+ ApiNavProps.path = StringProperty(name='path',
+ description='Enter bpy.ops.api_navigator to see the documentation',
+ default='bpy')
+
+ ApiNavProps.old_path = StringProperty(name='old_path', default='')
+
+ ApiNavProps.filter = StringProperty(name='filter',
+ description='Filter the resulting modules', default='')
+
+ ApiNavProps.reduce_to = IntProperty(name='Reduce to ',
+ description='Display a maximum number of x entries by pages',
+ default=10, min=1)
+
+ ApiNavProps.pages = IntProperty(name='Pages',
+ description='Display a Page', default=0, min=0)
+
+def delProperties():
+ del bpy.types.WindowManager.api_nav_props
+
+###### API Navigator parent class #######
+
+class ApiNavigator():
+ """Parent class for API Navigator"""
+
+
+ def generate_global_values(self):
+ """Populate the level attributes to display the panel buttons and the documentation"""
+ global tree_level, current_module, module_type, return_report, last_text
+
+ text = bpy.context.space_data.text
+ if text:
+ if text.name != 'api_doc_':
+ last_text = bpy.context.space_data.text.name
+ elif bpy.data.texts.__len__() < 2:
+ last_text = None
+ else :
+ last_text = None
+ bpy.context.window_manager.api_nav_props.pages = 0
+ get_root_module(bpy.context.window_manager.api_nav_props.path)
+ current_module = evaluate(bpy.context.window_manager.api_nav_props.path)
+ module_type = str(type(current_module))
+ return_report = str(current_module)
+ tree_level = get_tree_level()
+
+ if tree_level.__len__() > 30:
+ global too_long
+ too_long = True
+ else :
+ too_long = False
+
+ self.generate_api_doc()
+ return {'FINISHED'}
+
+
+ def generate_api_doc(self):
+ """Format the doc string for API Navigator"""
+ global current_module, api_doc_, return_report, module_type
+
+ path = bpy.context.window_manager.api_nav_props.path
+ line = "-" * (path.__len__()+2)
+ header = """\n\n\n\t\t%s\n\t %s\n\
+_____________________________________________\n\
+\n\
+Type : %s\n\
+\n\
+\n\
+Return : %s\n\
+_____________________________________________\n\
+\n\
+Doc:
+\n\
+""" % (path, line, module_type, return_report)
+ footer = "\n\
+_____________________________________________\n\
+\n\
+\n\
+\n\
+#############################################\n\
+# api_doc_ #\n\
+# Escape to toggle text #\n\
+# (F8 to reload modules if doesn't work) #\n\
+#############################################"
+ doc = current_module.__doc__
+ api_doc_ = header + str(doc) + footer
+ return {'FINISHED'}
+
+
+ def doc_text_datablock(self):
+ """Create the text databloc or overwrite it if it already exist"""
+ global api_doc_
+
+ space_data = bpy.context.space_data
+
+ try :
+ doc_text = bpy.data.texts['api_doc_']
+ space_data.text = doc_text
+ doc_text.clear()
+ except :
+ bpy.data.texts.new(name='api_doc_')
+ doc_text = bpy.data.texts['api_doc_']
+ space_data.text = doc_text
+
+ doc_text.write(text=api_doc_)
+ return {'FINISHED'}
+
+
+
+############ Operators ############
+
+class Update(ApiNavigator, bpy.types.Operator):
+ """Update the tree structure"""
+ bl_idname = "api_navigator.update"
+ bl_label = "API Navigator Update"
+
+
+ def execute(self, context):
+ if bpy.context.window_manager.api_nav_props.path != bpy.context.window_manager.api_nav_props.old_path:
+ fill_filter_mem()
+ bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path
+ update_filter(self)
+ self.generate_global_values()
+ self.doc_text_datablock()
+ return {'FINISHED'}
+ return {'FINISHED'}
+
+
+class BackToBpy(ApiNavigator, bpy.types.Operator):
+ """go back to module bpy"""
+ bl_idname = "api_navigator.back_to_bpy"
+ bl_label = "Back to bpy"
+
+ def execute(self, context):
+ fill_filter_mem()
+ if not bpy.context.window_manager.api_nav_props.path:
+ bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = 'bpy'
+ else :
+ bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = 'bpy'
+ update_filter(self)
+ self.generate_global_values()
+ self.doc_text_datablock()
+ return {'FINISHED'}
+
+
+class Down(ApiNavigator, bpy.types.Operator):
+ """go to this Module"""
+ bl_idname = "api_navigator.down"
+ bl_label = "API Navigator Down"
+ pointed_module = bpy.props.StringProperty(name='Current Module', default='')
+
+
+ def execute(self, context):
+ fill_filter_mem()
+
+ if not bpy.context.window_manager.api_nav_props.path:
+ bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = bpy.context.window_manager.api_nav_props.path + self.pointed_module
+ else :
+ bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = bpy.context.window_manager.api_nav_props.path + '.' + self.pointed_module
+
+ update_filter(self)
+ self.generate_global_values()
+ self.doc_text_datablock()
+ return {'FINISHED'}
+
+
+class Parent(ApiNavigator, bpy.types.Operator):
+ """go to Parent Module"""
+ bl_idname = "api_navigator.parent"
+ bl_label = "API Navigator Parent"
+
+
+ def execute(self, context):
+ path = bpy.context.window_manager.api_nav_props.path
+
+ if path:
+ fill_filter_mem()
+ bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = parent(bpy.context.window_manager.api_nav_props.path)
+ update_filter(self)
+ self.generate_global_values()
+ self.doc_text_datablock()
+
+ return {'FINISHED'}
+
+
+class ClearFilter(ApiNavigator, bpy.types.Operator):
+ """Clear the filter"""
+ bl_idname = 'api_navigator.clear_filter'
+ bl_label = 'API Nav clear filter'
+
+ def execute(self, context):
+ bpy.context.window_manager.api_nav_props.filter = ''
+ return {'FINISHED'}
+
+
+class FakeButton(ApiNavigator, bpy.types.Operator):
+ """The list is not displayed completely""" # only serve as an indicator
+ bl_idname = 'api_navigator.fake_button'
+ bl_label = ''
+
+
+class Subscript(ApiNavigator, bpy.types.Operator):
+ """Subscript to this Item"""
+ bl_idname = "api_navigator.subscript"
+ bl_label = "API Navigator Subscript"
+ subscription = bpy.props.StringProperty(name='', default='')
+
+ def execute(self, context):
+ fill_filter_mem()
+ bpy.context.window_manager.api_nav_props.old_path = bpy.context.window_manager.api_nav_props.path = bpy.context.window_manager.api_nav_props.path + '[' + self.subscription + ']'
+ update_filter(self)
+ self.generate_global_values()
+ self.doc_text_datablock()
+ return {'FINISHED'}
+
+
+class Toggle_doc(ApiNavigator, bpy.types.Operator):
+ """Toggle on or off api_doc_ Text"""
+ bl_idname = 'api_navigator.toggle_doc'
+ bl_label = 'Toggle api_doc_'
+
+
+ def execute(self, context):
+ global last_text
+
+ try :
+ if bpy.context.space_data.text.name != "api_doc_":
+ last_text = bpy.context.space_data.text.name
+ except : pass
+
+ try :
+ text = bpy.data.texts["api_doc_"]
+ bpy.data.texts["api_doc_"].clear()
+ bpy.data.texts.remove(text)
+ except KeyError:
+ self.doc_text_datablock()
+ return {'FINISHED'}
+
+ try :
+ text = bpy.data.texts[last_text]
+ bpy.context.space_data.text = text
+ #line = bpy.ops.text.line_number() # operator doesn't seems to work ???
+ #bpy.ops.text.jump(line=line)
+ return {'FINISHED'}
+ except : pass
+
+ bpy.context.space_data.text = None
+ return {'FINISHED'}
+
+############ UI Panels ############
+
+class OBJECT_PT_api_navigator(ApiNavigator, bpy.types.Panel):
+ bl_idname = 'api_navigator'
+ bl_space_type = "TEXT_EDITOR"
+ bl_region_type = "UI"
+ bl_label = "API Navigator"
+ bl_options = "DEFAULT_CLOSED"
+
+
+ columns = 3
+
+
+ def iterable_draw(self):
+ global tree_level, current_module
+
+ iterable = isiterable(current_module)
+
+ if iterable:
+ iter(current_module)
+ current_type = str(module_type)
+
+ if current_type == "<class 'str'>":
+ return {'FINISHED'}
+
+ col = self.layout
+ filter = bpy.context.window_manager.api_nav_props.filter
+ reduce_to = bpy.context.window_manager.api_nav_props.reduce_to * self.columns
+ pages = bpy.context.window_manager.api_nav_props.pages
+ page_index = reduce_to*pages
+ rank = 0
+ count = 0
+ i = 0
+ filtered = 0
+
+ if iterable == 'a':
+ current_type.__iter__()
+ collection = list(current_module.keys())
+ end = collection.__len__()
+ box = self.layout.box()
+ row = box.row()
+ row.label(text="Items", icon="DOTSDOWN")
+ box = box.box()
+ col = box.column(align=True)
+
+ while count < reduce_to and i < end:
+ mod = collection[i]
+ if filtered < page_index:
+ filtered += 1
+ i += 1
+ continue
+
+ if not (i % self.columns):
+ row = col.row()
+ row.operator('api_navigator.subscript', text=mod, emboss=False).subscription = '"' + mod + '"'
+ filtered += 1
+ i += 1
+ count += 1
+
+ elif iterable == 'b':
+ box = self.layout.box()
+ row = box.row()
+ row.label(text="Item Values", icon="OOPS")
+ box = box.box()
+ col = box.column(align=True)
+ collection = list(current_module)
+ end = collection.__len__()
+
+ while count < reduce_to and i < end:
+ mod = str(collection[i])
+ if filtered < page_index:
+ filtered += 1
+ i += 1
+ continue
+
+ if not (i % self.columns):
+ row = col.row()
+ row.operator('api_navigator.subscript', text=mod, emboss=False).subscription = str(i)
+ filtered += 1
+ i += 1
+ count += 1
+
+ too_long = end > 30
+
+ if too_long:
+ row = col.row()
+ row.prop(bpy.context.window_manager.api_nav_props, 'reduce_to')
+ row.operator('api_navigator.fake_button', text='', emboss=False, icon="DOTSDOWN")
+ row.prop(bpy.context.window_manager.api_nav_props, 'pages', text='Pages')
+
+ return {'FINISHED'}
+
+
+
+
+ def list_draw(self, t, pages, icon, label=None, emboss=False):
+ global tree_level, current_module
+
+ def reduced(too_long):
+
+ if too_long:
+ row = col.row()
+ row.prop(bpy.context.window_manager.api_nav_props, 'reduce_to')
+ row.operator('api_navigator.fake_button', text='', emboss=False, icon="DOTSDOWN")
+ row.prop(bpy.context.window_manager.api_nav_props, 'pages', text='Pages')
+
+ layout = self.layout
+
+ filter = bpy.context.window_manager.api_nav_props.filter
+
+ reduce_to = bpy.context.window_manager.api_nav_props.reduce_to * self.columns
+
+ page_index = reduce_to*pages
+
+
+ len = tree_level[t].__len__()
+ too_long = len > reduce_to
+
+ if len:
+ col = layout.column()
+ box = col.box()
+
+ row = box.row()
+ row.label(text=label, icon=icon)
+
+ if t < 2:
+ box = box.box()
+ row = box.row()
+ col = row.column(align=True)
+ i = 0
+ objects = 0
+ count = 0
+ filtered = 0
+
+ while count < reduce_to and i < len:
+ obj = tree_level[t][i]
+
+ if filter and filter not in obj:
+ i += 1
+ continue
+ elif filtered < page_index:
+ filtered += 1
+ i += 1
+ continue
+
+ if not (objects % self.columns):
+ row = col.row()
+ if t > 1:
+ row.operator("api_navigator.down", text=obj, emboss=emboss).pointed_module = obj
+ elif t == 0:
+ row.operator('api_navigator.subscript', text=str(obj), emboss=False).subscription = '"' + obj + '"'
+ else :
+ row.operator('api_navigator.subscript', text=str(obj), emboss=False).subscription = str(i)
+ filtered += 1
+ i += 1
+ objects += 1
+ count += 1
+
+ reduced(too_long)
+
+ return {'FINISHED'}
+
+
+ def draw(self, context):
+ global tree_level, current_module, module_type, return_report
+
+ bpy.ops.api_navigator.update()
+
+ st = bpy.context.space_data
+
+ ###### layout ######
+ layout = self.layout
+ col = layout.column()
+ layout.label(text='Tree Structure')
+ col = layout.column(align=True)
+ col.prop(bpy.context.window_manager.api_nav_props, 'path', text='')
+ row = col.row()
+ row.operator("api_navigator.parent", text="Parent", icon="BACK")
+ row.operator("api_navigator.back_to_bpy", text='', emboss=True, icon="FILE_PARENT")
+
+ col = layout.column()
+ row = col.row(align=True)
+ row.prop(bpy.context.window_manager.api_nav_props, 'filter')
+ row.operator('api_navigator.clear_filter', text='', icon='PANEL_CLOSE')
+
+ col = layout.column()
+
+ pages = bpy.context.window_manager.api_nav_props.pages
+ self.list_draw(0, pages, "DOTSDOWN", label="Items")
+ self.list_draw(1, pages, "DOTSDOWN", label="Item Values")
+ self.list_draw(2, pages, "PACKAGE", label="Modules", emboss=True)
+ self.list_draw(3, pages, "WORDWRAP_ON", label="Types", emboss=False)
+ self.list_draw(4, pages, "BUTS", label="Properties", emboss=False)
+ self.list_draw(5, pages, "OOPS", label="Structs and Functions")
+ self.list_draw(6, pages, "SCRIPTWIN", label="Methods and Functions")
+ self.list_draw(7, pages, "INFO", label="Attributes")
+ self.list_draw(8, pages, "ERROR", label="Inaccessible")
+
+
+########### Menu functions ###############
+
+
+def register_keymaps():
+ kc = bpy.context.window_manager.keyconfigs['Blender']
+ km = kc.keymaps.get("Text")
+ if km is None:
+ km = kc.keymaps.new(name="Text")
+ kmi = km.items.new('api_navigator.toggle_doc', 'ESC', 'PRESS')
+
+
+def unregister_keymaps():
+ km = bpy.data.window_managers["WinMan"].keyconfigs.default.keymaps["Text"]
+ kmi = km.items["api_navigator.toggle_doc"]
+ km.items.remove(kmi)
+
+
+def register():
+ class ApiNavProps(bpy.types.IDPropertyGroup):
+ """
+ Fake module like class.
+
+ bpy.context.window_manager.api_nav_props
+
+ """
+ pass
+
+
+ addProperties(ApiNavProps)
+ register_keymaps()
+ #print(get_tree_level())
+
+
+def unregister():
+ unregister_keymaps()
+ delProperties()
+
+
+
+if __name__ == '__main__':
+ register()