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

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'release/scripts/startup/bl_operators')
-rw-r--r--release/scripts/startup/bl_operators/add_mesh_torus.py138
-rw-r--r--release/scripts/startup/bl_operators/animsys_update.py696
-rw-r--r--release/scripts/startup/bl_operators/fcurve_euler_filter.py78
-rw-r--r--release/scripts/startup/bl_operators/image.py189
-rw-r--r--release/scripts/startup/bl_operators/mesh.py172
-rw-r--r--release/scripts/startup/bl_operators/nla.py170
-rw-r--r--release/scripts/startup/bl_operators/object.py563
-rw-r--r--release/scripts/startup/bl_operators/object_align.py280
-rw-r--r--release/scripts/startup/bl_operators/object_randomize_transform.py147
-rw-r--r--release/scripts/startup/bl_operators/presets.py353
-rw-r--r--release/scripts/startup/bl_operators/screen_play_rendered_anim.py142
-rw-r--r--release/scripts/startup/bl_operators/sequencer.py134
-rw-r--r--release/scripts/startup/bl_operators/uvcalc_follow_active.py250
-rw-r--r--release/scripts/startup/bl_operators/uvcalc_lightmap.py582
-rw-r--r--release/scripts/startup/bl_operators/uvcalc_smart_project.py1141
-rw-r--r--release/scripts/startup/bl_operators/vertexpaint_dirt.py176
-rw-r--r--release/scripts/startup/bl_operators/wm.py848
17 files changed, 6059 insertions, 0 deletions
diff --git a/release/scripts/startup/bl_operators/add_mesh_torus.py b/release/scripts/startup/bl_operators/add_mesh_torus.py
new file mode 100644
index 00000000000..460330a56a1
--- /dev/null
+++ b/release/scripts/startup/bl_operators/add_mesh_torus.py
@@ -0,0 +1,138 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+import bpy
+import mathutils
+
+
+def add_torus(major_rad, minor_rad, major_seg, minor_seg):
+ from math import cos, sin, pi
+
+ Vector = mathutils.Vector
+ Quaternion = mathutils.Quaternion
+
+ PI_2 = pi * 2.0
+ z_axis = 0.0, 0.0, 1.0
+
+ verts = []
+ faces = []
+ i1 = 0
+ tot_verts = major_seg * minor_seg
+ for major_index in range(major_seg):
+ quat = Quaternion(z_axis, (major_index / major_seg) * PI_2)
+
+ for minor_index in range(minor_seg):
+ angle = 2 * pi * minor_index / minor_seg
+
+ vec = Vector((major_rad + (cos(angle) * minor_rad), 0.0,
+ (sin(angle) * minor_rad))) * quat
+
+ verts.extend(vec[:])
+
+ if minor_index + 1 == minor_seg:
+ i2 = (major_index) * minor_seg
+ i3 = i1 + minor_seg
+ i4 = i2 + minor_seg
+
+ else:
+ i2 = i1 + 1
+ i3 = i1 + minor_seg
+ i4 = i3 + 1
+
+ if i2 >= tot_verts:
+ i2 = i2 - tot_verts
+ if i3 >= tot_verts:
+ i3 = i3 - tot_verts
+ if i4 >= tot_verts:
+ i4 = i4 - tot_verts
+
+ # stupid eekadoodle
+ if i2:
+ faces.extend([i1, i3, i4, i2])
+ else:
+ faces.extend([i2, i1, i3, i4])
+
+ i1 += 1
+
+ return verts, faces
+
+from bpy.props import FloatProperty, IntProperty, BoolProperty, FloatVectorProperty
+
+
+class AddTorus(bpy.types.Operator):
+ '''Add a torus mesh'''
+ bl_idname = "mesh.primitive_torus_add"
+ bl_label = "Add Torus"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ major_radius = FloatProperty(name="Major Radius",
+ description="Radius from the origin to the center of the cross sections",
+ default=1.0, min=0.01, max=100.0)
+ minor_radius = FloatProperty(name="Minor Radius",
+ description="Radius of the torus' cross section",
+ default=0.25, min=0.01, max=100.0)
+ major_segments = IntProperty(name="Major Segments",
+ description="Number of segments for the main ring of the torus",
+ default=48, min=3, max=256)
+ minor_segments = IntProperty(name="Minor Segments",
+ description="Number of segments for the minor ring of the torus",
+ default=12, min=3, max=256)
+ use_abso = BoolProperty(name="Use Int+Ext Controls",
+ description="Use the Int / Ext controls for torus dimensions",
+ default=False)
+ abso_major_rad = FloatProperty(name="Exterior Radius",
+ description="Total Exterior Radius of the torus",
+ default=1.0, min=0.01, max=100.0)
+ abso_minor_rad = FloatProperty(name="Inside Radius",
+ description="Total Interior Radius of the torus",
+ default=0.5, min=0.01, max=100.0)
+
+ # generic transform props
+ view_align = BoolProperty(name="Align to View",
+ default=False)
+ location = FloatVectorProperty(name="Location",
+ subtype='TRANSLATION')
+ rotation = FloatVectorProperty(name="Rotation",
+ subtype='EULER')
+
+ def execute(self, context):
+
+ if self.use_abso == True:
+ extra_helper = (self.abso_major_rad - self.abso_minor_rad) * 0.5
+ self.major_radius = self.abso_minor_rad + extra_helper
+ self.minor_radius = extra_helper
+
+ verts_loc, faces = add_torus(self.major_radius,
+ self.minor_radius,
+ self.major_segments,
+ self.minor_segments)
+
+ mesh = bpy.data.meshes.new("Torus")
+
+ mesh.vertices.add(len(verts_loc) // 3)
+ mesh.faces.add(len(faces) // 4)
+
+ mesh.vertices.foreach_set("co", verts_loc)
+ mesh.faces.foreach_set("vertices_raw", faces)
+ mesh.update()
+
+ import add_object_utils
+ add_object_utils.object_data_add(context, mesh, operator=self)
+
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/animsys_update.py b/release/scripts/startup/bl_operators/animsys_update.py
new file mode 100644
index 00000000000..eb14ab7eb72
--- /dev/null
+++ b/release/scripts/startup/bl_operators/animsys_update.py
@@ -0,0 +1,696 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+data_path_update = [
+ ("ClothCollisionSettings", "min_distance", "distance_min"),
+ ("ClothCollisionSettings", "self_min_distance", "self_distance_min"),
+ ("ClothCollisionSettings", "enable_collision", "use_collision"),
+ ("ClothCollisionSettings", "enable_self_collision", "use_self_collision"),
+ ("ClothSettings", "pin_cloth", "use_pin_cloth"),
+ ("ClothSettings", "stiffness_scaling", "use_stiffness_scale"),
+ ("CollisionSettings", "random_damping", "damping_random"),
+ ("CollisionSettings", "random_friction", "friction_random"),
+ ("CollisionSettings", "inner_thickness", "thickness_inner"),
+ ("CollisionSettings", "outer_thickness", "thickness_outer"),
+ ("CollisionSettings", "kill_particles", "use_particle_kill"),
+ ("Constraint", "proxy_local", "is_proxy_local"),
+ ("ActionConstraint", "maximum", "max"),
+ ("ActionConstraint", "minimum", "min"),
+ ("FollowPathConstraint", "use_fixed_position", "use_fixed_location"),
+ ("KinematicConstraint", "chain_length", "chain_count"),
+ ("KinematicConstraint", "pos_lock_x", "lock_location_x"),
+ ("KinematicConstraint", "pos_lock_y", "lock_location_y"),
+ ("KinematicConstraint", "pos_lock_z", "lock_location_z"),
+ ("KinematicConstraint", "rot_lock_x", "lock_rotation_x"),
+ ("KinematicConstraint", "rot_lock_y", "lock_rotation_y"),
+ ("KinematicConstraint", "rot_lock_z", "lock_rotation_z"),
+ ("KinematicConstraint", "axis_reference", "reference_axis"),
+ ("KinematicConstraint", "use_position", "use_location"),
+ ("LimitLocationConstraint", "maximum_x", "max_x"),
+ ("LimitLocationConstraint", "maximum_y", "max_y"),
+ ("LimitLocationConstraint", "maximum_z", "max_z"),
+ ("LimitLocationConstraint", "minimum_x", "min_x"),
+ ("LimitLocationConstraint", "minimum_y", "min_y"),
+ ("LimitLocationConstraint", "minimum_z", "min_z"),
+ ("LimitLocationConstraint", "use_maximum_x", "use_max_x"),
+ ("LimitLocationConstraint", "use_maximum_y", "use_max_y"),
+ ("LimitLocationConstraint", "use_maximum_z", "use_max_z"),
+ ("LimitLocationConstraint", "use_minimum_x", "use_min_x"),
+ ("LimitLocationConstraint", "use_minimum_y", "use_min_y"),
+ ("LimitLocationConstraint", "use_minimum_z", "use_min_z"),
+ ("LimitLocationConstraint", "limit_transform", "use_transform_limit"),
+ ("LimitRotationConstraint", "maximum_x", "max_x"),
+ ("LimitRotationConstraint", "maximum_y", "max_y"),
+ ("LimitRotationConstraint", "maximum_z", "max_z"),
+ ("LimitRotationConstraint", "minimum_x", "min_x"),
+ ("LimitRotationConstraint", "minimum_y", "min_y"),
+ ("LimitRotationConstraint", "minimum_z", "min_z"),
+ ("LimitRotationConstraint", "limit_transform", "use_transform_limit"),
+ ("LimitScaleConstraint", "maximum_x", "max_x"),
+ ("LimitScaleConstraint", "maximum_y", "max_y"),
+ ("LimitScaleConstraint", "maximum_z", "max_z"),
+ ("LimitScaleConstraint", "minimum_x", "min_x"),
+ ("LimitScaleConstraint", "minimum_y", "min_y"),
+ ("LimitScaleConstraint", "minimum_z", "min_z"),
+ ("LimitScaleConstraint", "use_maximum_x", "use_max_x"),
+ ("LimitScaleConstraint", "use_maximum_y", "use_max_y"),
+ ("LimitScaleConstraint", "use_maximum_z", "use_max_z"),
+ ("LimitScaleConstraint", "use_minimum_x", "use_min_x"),
+ ("LimitScaleConstraint", "use_minimum_y", "use_min_y"),
+ ("LimitScaleConstraint", "use_minimum_z", "use_min_z"),
+ ("LimitScaleConstraint", "limit_transform", "use_transform_limit"),
+ ("PivotConstraint", "enabled_rotation_range", "rotation_range"),
+ ("PivotConstraint", "use_relative_position", "use_relative_location"),
+ ("PythonConstraint", "number_of_targets", "target_count"),
+ ("SplineIKConstraint", "chain_length", "chain_count"),
+ ("SplineIKConstraint", "chain_offset", "use_chain_offset"),
+ ("SplineIKConstraint", "even_divisions", "use_even_divisions"),
+ ("SplineIKConstraint", "y_stretch", "use_y_stretch"),
+ ("SplineIKConstraint", "xz_scaling_mode", "xz_scale_mode"),
+ ("StretchToConstraint", "original_length", "rest_length"),
+ ("TrackToConstraint", "target_z", "use_target_z"),
+ ("TransformConstraint", "extrapolate_motion", "use_motion_extrapolate"),
+ ("FieldSettings", "do_location", "apply_to_location"),
+ ("FieldSettings", "do_rotation", "apply_to_rotation"),
+ ("FieldSettings", "maximum_distance", "distance_max"),
+ ("FieldSettings", "minimum_distance", "distance_min"),
+ ("FieldSettings", "radial_maximum", "radial_max"),
+ ("FieldSettings", "radial_minimum", "radial_min"),
+ ("FieldSettings", "force_2d", "use_2d_force"),
+ ("FieldSettings", "do_absorption", "use_absorption"),
+ ("FieldSettings", "global_coordinates", "use_global_coords"),
+ ("FieldSettings", "guide_path_add", "use_guide_path_add"),
+ ("FieldSettings", "multiple_springs", "use_multiple_springs"),
+ ("FieldSettings", "use_coordinates", "use_object_coords"),
+ ("FieldSettings", "root_coordinates", "use_root_coords"),
+ ("ControlFluidSettings", "reverse_frames", "use_reverse_frames"),
+ ("DomainFluidSettings", "real_world_size", "simulation_scale"),
+ ("DomainFluidSettings", "surface_smoothing", "surface_smooth"),
+ ("DomainFluidSettings", "reverse_frames", "use_reverse_frames"),
+ ("DomainFluidSettings", "generate_speed_vectors", "use_speed_vectors"),
+ ("DomainFluidSettings", "override_time", "use_time_override"),
+ ("FluidFluidSettings", "export_animated_mesh", "use_animated_mesh"),
+ ("InflowFluidSettings", "export_animated_mesh", "use_animated_mesh"),
+ ("InflowFluidSettings", "local_coordinates", "use_local_coords"),
+ ("ObstacleFluidSettings", "export_animated_mesh", "use_animated_mesh"),
+ ("OutflowFluidSettings", "export_animated_mesh", "use_animated_mesh"),
+ ("ParticleFluidSettings", "drops", "use_drops"),
+ ("ParticleFluidSettings", "floats", "use_floats"),
+ ("Armature", "drawtype", "draw_type"),
+ ("Armature", "layer_protection", "layers_protected"),
+ ("Armature", "auto_ik", "use_auto_ik"),
+ ("Armature", "delay_deform", "use_deform_delay"),
+ ("Armature", "deform_envelope", "use_deform_envelopes"),
+ ("Armature", "deform_quaternion", "use_deform_preserve_volume"),
+ ("Armature", "deform_vertexgroups", "use_deform_vertex_groups"),
+ ("Armature", "x_axis_mirror", "use_mirror_x"),
+ ("Curve", "width", "offset"),
+ ("Image", "animation_speed", "fps"),
+ ("Image", "animation_end", "frame_end"),
+ ("Image", "animation_start", "frame_start"),
+ ("Image", "animated", "use_animation"),
+ ("Image", "clamp_x", "use_clamp_x"),
+ ("Image", "clamp_y", "use_clamp_y"),
+ ("Image", "premultiply", "use_premultiply"),
+ ("AreaLamp", "shadow_ray_sampling_method", "shadow_ray_sample_method"),
+ ("AreaLamp", "only_shadow", "use_only_shadow"),
+ ("AreaLamp", "shadow_layer", "use_shadow_layer"),
+ ("AreaLamp", "umbra", "use_umbra"),
+ ("PointLamp", "shadow_ray_sampling_method", "shadow_ray_sample_method"),
+ ("PointLamp", "only_shadow", "use_only_shadow"),
+ ("PointLamp", "shadow_layer", "use_shadow_layer"),
+ ("PointLamp", "sphere", "use_sphere"),
+ ("SpotLamp", "shadow_ray_sampling_method", "shadow_ray_sample_method"),
+ ("SpotLamp", "auto_clip_end", "use_auto_clip_end"),
+ ("SpotLamp", "auto_clip_start", "use_auto_clip_start"),
+ ("SpotLamp", "only_shadow", "use_only_shadow"),
+ ("SpotLamp", "shadow_layer", "use_shadow_layer"),
+ ("SpotLamp", "sphere", "use_sphere"),
+ ("SunLamp", "only_shadow", "use_only_shadow"),
+ ("SunLamp", "shadow_layer", "use_shadow_layer"),
+ ("Material", "z_offset", "offset_z"),
+ ("Material", "shadow_casting_alpha", "shadow_cast_alpha"),
+ ("Material", "cast_approximate", "use_cast_approximate"),
+ ("Material", "cast_buffer_shadows", "use_cast_buffer_shadows"),
+ ("Material", "cast_shadows_only", "use_cast_shadows_only"),
+ ("Material", "face_texture", "use_face_texture"),
+ ("Material", "face_texture_alpha", "use_face_texture_alpha"),
+ ("Material", "full_oversampling", "use_full_oversampling"),
+ ("Material", "light_group_exclusive", "use_light_group_exclusive"),
+ ("Material", "object_color", "use_object_color"),
+ ("Material", "only_shadow", "use_only_shadow"),
+ ("Material", "ray_shadow_bias", "use_ray_shadow_bias"),
+ ("Material", "traceable", "use_raytrace"),
+ ("Material", "shadeless", "use_shadeless"),
+ ("Material", "tangent_shading", "use_tangent_shading"),
+ ("Material", "transparency", "use_transparency"),
+ ("Material", "receive_transparent_shadows", "use_transparent_shadows"),
+ ("Material", "vertex_color_light", "use_vertex_color_light"),
+ ("Material", "vertex_color_paint", "use_vertex_color_paint"),
+ ("Mesh", "autosmooth_angle", "auto_smooth_angle"),
+ ("Mesh", "autosmooth", "use_auto_smooth"),
+ ("Object", "max_draw_type", "draw_type"),
+ ("Object", "use_dupli_verts_rotation", "use_dupli_vertices_rotation"),
+ ("Object", "shape_key_edit_mode", "use_shape_key_edit_mode"),
+ ("Object", "slow_parent", "use_slow_parent"),
+ ("Object", "time_offset_add_parent", "use_time_offset_add_parent"),
+ ("Object", "time_offset_edit", "use_time_offset_edit"),
+ ("Object", "time_offset_parent", "use_time_offset_parent"),
+ ("Object", "time_offset_particle", "use_time_offset_particle"),
+ ("ParticleSettings", "adaptive_pix", "adaptive_pixel"),
+ ("ParticleSettings", "child_effector", "apply_effector_to_children"),
+ ("ParticleSettings", "child_guide", "apply_guide_to_children"),
+ ("ParticleSettings", "billboard_split_offset", "billboard_offset_split"),
+ ("ParticleSettings", "billboard_random_tilt", "billboard_tilt_random"),
+ ("ParticleSettings", "child_length_thres", "child_length_threshold"),
+ ("ParticleSettings", "child_random_size", "child_size_random"),
+ ("ParticleSettings", "clumppow", "clump_shape"),
+ ("ParticleSettings", "damp_factor", "damping"),
+ ("ParticleSettings", "draw_as", "draw_method"),
+ ("ParticleSettings", "random_factor", "factor_random"),
+ ("ParticleSettings", "grid_invert", "invert_grid"),
+ ("ParticleSettings", "random_length", "length_random"),
+ ("ParticleSettings", "random_lifetime", "lifetime_random"),
+ ("ParticleSettings", "billboard_lock", "lock_billboard"),
+ ("ParticleSettings", "boids_2d", "lock_boids_to_surface"),
+ ("ParticleSettings", "object_aligned_factor", "object_align_factor"),
+ ("ParticleSettings", "random_phase_factor", "phase_factor_random"),
+ ("ParticleSettings", "ren_as", "render_type"),
+ ("ParticleSettings", "rendered_child_nbr", "rendered_child_count"),
+ ("ParticleSettings", "random_rotation_factor", "rotation_factor_random"),
+ ("ParticleSettings", "rough1", "roughness_1"),
+ ("ParticleSettings", "rough1_size", "roughness_1_size"),
+ ("ParticleSettings", "rough2", "roughness_2"),
+ ("ParticleSettings", "rough2_size", "roughness_2_size"),
+ ("ParticleSettings", "rough2_thres", "roughness_2_threshold"),
+ ("ParticleSettings", "rough_end_shape", "roughness_end_shape"),
+ ("ParticleSettings", "rough_endpoint", "roughness_endpoint"),
+ ("ParticleSettings", "random_size", "size_random"),
+ ("ParticleSettings", "abs_path_time", "use_absolute_path_time"),
+ ("ParticleSettings", "animate_branching", "use_animate_branching"),
+ ("ParticleSettings", "branching", "use_branching"),
+ ("ParticleSettings", "died", "use_dead"),
+ ("ParticleSettings", "die_on_collision", "use_die_on_collision"),
+ ("ParticleSettings", "rotation_dynamic", "use_dynamic_rotation"),
+ ("ParticleSettings", "even_distribution", "use_even_distribution"),
+ ("ParticleSettings", "rand_group", "use_group_pick_random"),
+ ("ParticleSettings", "hair_bspline", "use_hair_bspline"),
+ ("ParticleSettings", "sizemass", "use_multiply_size_mass"),
+ ("ParticleSettings", "react_multiple", "use_react_multiple"),
+ ("ParticleSettings", "react_start_end", "use_react_start_end"),
+ ("ParticleSettings", "render_adaptive", "use_render_adaptive"),
+ ("ParticleSettings", "self_effect", "use_self_effect"),
+ ("ParticleSettings", "enable_simplify", "use_simplify"),
+ ("ParticleSettings", "size_deflect", "use_size_deflect"),
+ ("ParticleSettings", "render_strand", "use_strand_primitive"),
+ ("ParticleSettings", "symmetric_branching", "use_symmetric_branching"),
+ ("ParticleSettings", "velocity_length", "use_velocity_length"),
+ ("ParticleSettings", "whole_group", "use_whole_group"),
+ ("CloudsTexture", "noise_size", "noise_scale"),
+ ("DistortedNoiseTexture", "noise_size", "noise_scale"),
+ ("EnvironmentMapTexture", "filter_size_minimum", "use_filter_size_min"),
+ ("EnvironmentMapTexture", "mipmap_gauss", "use_mipmap_gauss"),
+ ("ImageTexture", "calculate_alpha", "use_calculate_alpha"),
+ ("ImageTexture", "checker_even", "use_checker_even"),
+ ("ImageTexture", "checker_odd", "use_checker_odd"),
+ ("ImageTexture", "filter_size_minimum", "use_filter_size_min"),
+ ("ImageTexture", "flip_axis", "use_flip_axis"),
+ ("ImageTexture", "mipmap_gauss", "use_mipmap_gauss"),
+ ("ImageTexture", "mirror_x", "use_mirror_x"),
+ ("ImageTexture", "mirror_y", "use_mirror_y"),
+ ("ImageTexture", "normal_map", "use_normal_map"),
+ ("MarbleTexture", "noise_size", "noise_scale"),
+ ("MarbleTexture", "noisebasis2", "noisebasis_2"),
+ ("MusgraveTexture", "highest_dimension", "dimension_max"),
+ ("MusgraveTexture", "noise_size", "noise_scale"),
+ ("StucciTexture", "noise_size", "noise_scale"),
+ ("VoronoiTexture", "coloring", "color_mode"),
+ ("VoronoiTexture", "noise_size", "noise_scale"),
+ ("WoodTexture", "noise_size", "noise_scale"),
+ ("WoodTexture", "noisebasis2", "noisebasis_2"),
+ ("World", "blend_sky", "use_sky_blend"),
+ ("World", "paper_sky", "use_sky_paper"),
+ ("World", "real_sky", "use_sky_real"),
+ ("ImageUser", "auto_refresh", "use_auto_refresh"),
+ ("MaterialHalo", "flares_sub", "flare_subflare_count"),
+ ("MaterialHalo", "flare_subsize", "flare_subflare_size"),
+ ("MaterialHalo", "line_number", "line_count"),
+ ("MaterialHalo", "rings", "ring_count"),
+ ("MaterialHalo", "star_tips", "star_tip_count"),
+ ("MaterialHalo", "xalpha", "use_extreme_alpha"),
+ ("MaterialHalo", "flare_mode", "use_flare_mode"),
+ ("MaterialHalo", "vertex_normal", "use_vertex_normal"),
+ ("MaterialPhysics", "align_to_normal", "use_normal_align"),
+ ("MaterialStrand", "min_size", "size_min"),
+ ("MaterialStrand", "blender_units", "use_blender_units"),
+ ("MaterialStrand", "surface_diffuse", "use_surface_diffuse"),
+ ("MaterialStrand", "tangent_shading", "use_tangent_shading"),
+ ("MaterialSubsurfaceScattering", "error_tolerance", "error_threshold"),
+ ("MaterialVolume", "depth_cutoff", "depth_threshold"),
+ ("MaterialVolume", "lighting_mode", "light_method"),
+ ("MaterialVolume", "step_calculation", "step_method"),
+ ("MaterialVolume", "external_shadows", "use_external_shadows"),
+ ("MaterialVolume", "light_cache", "use_light_cache"),
+ ("ArmatureModifier", "multi_modifier", "use_multi_modifier"),
+ ("ArrayModifier", "constant_offset_displacement", "constant_offset_displace"),
+ ("ArrayModifier", "merge_distance", "merge_threshold"),
+ ("ArrayModifier", "relative_offset_displacement", "relative_offset_displace"),
+ ("ArrayModifier", "constant_offset", "use_constant_offset"),
+ ("ArrayModifier", "merge_adjacent_vertices", "use_merge_vertices"),
+ ("ArrayModifier", "merge_end_vertices", "use_merge_vertices_cap"),
+ ("ArrayModifier", "add_offset_object", "use_object_offset"),
+ ("ArrayModifier", "relative_offset", "use_relative_offset"),
+ ("BevelModifier", "only_vertices", "use_only_vertices"),
+ ("CastModifier", "from_radius", "use_radius_as_size"),
+ ("DisplaceModifier", "midlevel", "mid_level"),
+ ("DisplaceModifier", "texture_coordinates", "texture_coords"),
+ ("EdgeSplitModifier", "use_sharp", "use_edge_sharp"),
+ ("ExplodeModifier", "split_edges", "use_edge_split"),
+ ("MirrorModifier", "merge_limit", "merge_threshold"),
+ ("MirrorModifier", "mirror_u", "use_mirror_u"),
+ ("MirrorModifier", "mirror_v", "use_mirror_v"),
+ ("MirrorModifier", "mirror_vertex_groups", "use_mirror_vertex_groups"),
+ ("ParticleInstanceModifier", "particle_system_number", "particle_system_index"),
+ ("ParticleInstanceModifier", "keep_shape", "use_preserve_shape"),
+ ("ShrinkwrapModifier", "cull_back_faces", "use_cull_back_faces"),
+ ("ShrinkwrapModifier", "cull_front_faces", "use_cull_front_faces"),
+ ("ShrinkwrapModifier", "keep_above_surface", "use_keep_above_surface"),
+ ("SimpleDeformModifier", "lock_x_axis", "lock_x"),
+ ("SimpleDeformModifier", "lock_y_axis", "lock_y"),
+ ("SmokeModifier", "smoke_type", "type"),
+ ("SubsurfModifier", "subsurf_uv", "use_subsurf_uv"),
+ ("UVProjectModifier", "num_projectors", "projector_count"),
+ ("UVProjectModifier", "override_image", "use_image_override"),
+ ("WaveModifier", "texture_coordinates", "texture_coords"),
+ ("WaveModifier", "x_normal", "use_normal_x"),
+ ("WaveModifier", "y_normal", "use_normal_y"),
+ ("WaveModifier", "z_normal", "use_normal_z"),
+ ("NlaStrip", "blending", "blend_type"),
+ ("NlaStrip", "animated_influence", "use_animated_influence"),
+ ("NlaStrip", "animated_time", "use_animated_time"),
+ ("NlaStrip", "animated_time_cyclic", "use_animated_time_cyclic"),
+ ("NlaStrip", "auto_blending", "use_auto_blend"),
+ ("CompositorNodeAlphaOver", "convert_premul", "use_premultiply"),
+ ("CompositorNodeBlur", "sizex", "size_x"),
+ ("CompositorNodeBlur", "sizey", "size_y"),
+ ("CompositorNodeChannelMatte", "algorithm", "limit_method"),
+ ("CompositorNodeChromaMatte", "acceptance", "tolerance"),
+ ("CompositorNodeColorBalance", "correction_formula", "correction_method"),
+ ("CompositorNodeColorSpill", "algorithm", "limit_method"),
+ ("CompositorNodeColorSpill", "unspill", "use_unspill"),
+ ("CompositorNodeCrop", "x2", "max_x"),
+ ("CompositorNodeCrop", "y2", "max_y"),
+ ("CompositorNodeCrop", "x1", "min_x"),
+ ("CompositorNodeCrop", "y1", "min_y"),
+ ("CompositorNodeCrop", "crop_size", "use_crop_size"),
+ ("CompositorNodeDefocus", "max_blur", "blur_max"),
+ ("CompositorNodeDefocus", "gamma_correction", "use_gamma_correction"),
+ ("CompositorNodeGlare", "rotate_45", "use_rotate_45"),
+ ("CompositorNodeImage", "auto_refresh", "use_auto_refresh"),
+ ("CompositorNodeLensdist", "projector", "use_projector"),
+ ("CompositorNodeVecBlur", "max_speed", "speed_max"),
+ ("CompositorNodeVecBlur", "min_speed", "speed_min"),
+ ("ShaderNodeMapping", "maximum", "max"),
+ ("ShaderNodeMapping", "minimum", "min"),
+ ("ShaderNodeMapping", "clamp_maximum", "use_max"),
+ ("ShaderNodeMapping", "clamp_minimum", "use_min"),
+ ("VertexPaint", "all_faces", "use_all_faces"),
+ ("VertexPaint", "spray", "use_spray"),
+ ("ParticleEdit", "add_keys", "default_key_count"),
+ ("ParticleEdit", "selection_mode", "select_mode"),
+ ("ParticleEdit", "auto_velocity", "use_auto_velocity"),
+ ("ParticleEdit", "add_interpolate", "use_default_interpolate"),
+ ("ParticleEdit", "emitter_deflect", "use_emitter_deflect"),
+ ("ParticleEdit", "fade_time", "use_fade_time"),
+ ("ParticleEdit", "keep_lengths", "use_preserve_length"),
+ ("ParticleEdit", "keep_root", "use_preserve_root"),
+ ("ParticleSystem", "vertex_group_clump_negate", "invert_vertex_group_clump"),
+ ("ParticleSystem", "vertex_group_density_negate", "invert_vertex_group_density"),
+ ("ParticleSystem", "vertex_group_field_negate", "invert_vertex_group_field"),
+ ("ParticleSystem", "vertex_group_kink_negate", "invert_vertex_group_kink"),
+ ("ParticleSystem", "vertex_group_length_negate", "invert_vertex_group_length"),
+ ("ParticleSystem", "vertex_group_rotation_negate", "invert_vertex_group_rotation"),
+ ("ParticleSystem", "vertex_group_roughness1_negate", "invert_vertex_group_roughness_1"),
+ ("ParticleSystem", "vertex_group_roughness2_negate", "invert_vertex_group_roughness_2"),
+ ("ParticleSystem", "vertex_group_roughness_end_negate", "invert_vertex_group_roughness_end"),
+ ("ParticleSystem", "vertex_group_size_negate", "invert_vertex_group_size"),
+ ("ParticleSystem", "vertex_group_tangent_negate", "invert_vertex_group_tangent"),
+ ("ParticleSystem", "vertex_group_velocity_negate", "invert_vertex_group_velocity"),
+ ("ParticleSystem", "hair_dynamics", "use_hair_dynamics"),
+ ("ParticleSystem", "keyed_timing", "use_keyed_timing"),
+ ("PointDensity", "falloff_softness", "falloff_soft"),
+ ("PointDensity", "particle_cache", "particle_cache_space"),
+ ("PointDensity", "turbulence_size", "turbulence_scale"),
+ ("PointDensity", "turbulence", "use_turbulence"),
+ ("PointDensity", "vertices_cache", "vertex_cache_space"),
+ ("PoseBone", "ik_lin_weight", "ik_linear_weight"),
+ ("PoseBone", "ik_rot_weight", "ik_rotation_weight"),
+ ("PoseBone", "ik_limit_x", "use_ik_limit_x"),
+ ("PoseBone", "ik_limit_y", "use_ik_limit_y"),
+ ("PoseBone", "ik_limit_z", "use_ik_limit_z"),
+ ("PoseBone", "ik_lin_control", "use_ik_linear_control"),
+ ("PoseBone", "ik_rot_control", "use_ik_rotation_control"),
+ ("Bone", "use_hinge", "use_inherit_rotation"),
+ ("SPHFluidSettings", "spring_k", "spring_force"),
+ ("SPHFluidSettings", "stiffness_k", "stiffness"),
+ ("SPHFluidSettings", "stiffness_knear", "stiffness_near"),
+ ("SceneGameData", "framing_color", "frame_color"),
+ ("SceneGameData", "framing_type", "frame_type"),
+ ("SceneGameData", "eye_separation", "stereo_eye_separation"),
+ ("SceneGameData", "activity_culling", "use_activity_culling"),
+ ("SceneGameData", "auto_start", "use_auto_start"),
+ ("SceneGameData", "glsl_extra_textures", "use_glsl_extra_textures"),
+ ("SceneGameData", "glsl_lights", "use_glsl_lights"),
+ ("SceneGameData", "glsl_nodes", "use_glsl_nodes"),
+ ("SceneGameData", "glsl_ramps", "use_glsl_ramps"),
+ ("SceneGameData", "glsl_shaders", "use_glsl_shaders"),
+ ("SceneGameData", "glsl_shadows", "use_glsl_shadows"),
+ ("Sequence", "blend_opacity", "blend_alpha"),
+ ("Sequence", "blend_mode", "blend_type"),
+ ("Sequence", "frame_final_length", "frame_final_duration"),
+ ("Sequence", "use_effect_default_fade", "use_default_fade"),
+ ("SequenceColorBalance", "inverse_gain", "invert_gain"),
+ ("SequenceColorBalance", "inverse_gamma", "invert_gamma"),
+ ("SequenceColorBalance", "inverse_lift", "invert_lift"),
+ ("EffectSequence", "multiply_colors", "color_multiply"),
+ ("EffectSequence", "de_interlace", "use_deinterlace"),
+ ("EffectSequence", "flip_x", "use_flip_x"),
+ ("EffectSequence", "flip_y", "use_flip_y"),
+ ("EffectSequence", "convert_float", "use_float"),
+ ("EffectSequence", "premultiply", "use_premultiply"),
+ ("EffectSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
+ ("EffectSequence", "proxy_custom_file", "use_proxy_custom_file"),
+ ("EffectSequence", "reverse_frames", "use_reverse_frames"),
+ ("GlowSequence", "blur_distance", "blur_radius"),
+ ("GlowSequence", "only_boost", "use_only_boost"),
+ ("SpeedControlSequence", "curve_compress_y", "use_curve_compress_y"),
+ ("SpeedControlSequence", "curve_velocity", "use_curve_velocity"),
+ ("SpeedControlSequence", "frame_blending", "use_frame_blend"),
+ ("TransformSequence", "uniform_scale", "use_uniform_scale"),
+ ("ImageSequence", "animation_end_offset", "animation_offset_end"),
+ ("ImageSequence", "animation_start_offset", "animation_offset_start"),
+ ("ImageSequence", "multiply_colors", "color_multiply"),
+ ("ImageSequence", "de_interlace", "use_deinterlace"),
+ ("ImageSequence", "flip_x", "use_flip_x"),
+ ("ImageSequence", "flip_y", "use_flip_y"),
+ ("ImageSequence", "convert_float", "use_float"),
+ ("ImageSequence", "premultiply", "use_premultiply"),
+ ("ImageSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
+ ("ImageSequence", "proxy_custom_file", "use_proxy_custom_file"),
+ ("ImageSequence", "reverse_frames", "use_reverse_frames"),
+ ("MetaSequence", "animation_end_offset", "animation_offset_end"),
+ ("MetaSequence", "animation_start_offset", "animation_offset_start"),
+ ("MetaSequence", "multiply_colors", "color_multiply"),
+ ("MetaSequence", "de_interlace", "use_deinterlace"),
+ ("MetaSequence", "flip_x", "use_flip_x"),
+ ("MetaSequence", "flip_y", "use_flip_y"),
+ ("MetaSequence", "convert_float", "use_float"),
+ ("MetaSequence", "premultiply", "use_premultiply"),
+ ("MetaSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
+ ("MetaSequence", "proxy_custom_file", "use_proxy_custom_file"),
+ ("MetaSequence", "reverse_frames", "use_reverse_frames"),
+ ("MovieSequence", "animation_end_offset", "animation_offset_end"),
+ ("MovieSequence", "animation_start_offset", "animation_offset_start"),
+ ("MovieSequence", "multiply_colors", "color_multiply"),
+ ("MovieSequence", "de_interlace", "use_deinterlace"),
+ ("MovieSequence", "flip_x", "use_flip_x"),
+ ("MovieSequence", "flip_y", "use_flip_y"),
+ ("MovieSequence", "convert_float", "use_float"),
+ ("MovieSequence", "premultiply", "use_premultiply"),
+ ("MovieSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
+ ("MovieSequence", "proxy_custom_file", "use_proxy_custom_file"),
+ ("MovieSequence", "reverse_frames", "use_reverse_frames"),
+ ("MulticamSequence", "animation_end_offset", "animation_offset_end"),
+ ("MulticamSequence", "animation_start_offset", "animation_offset_start"),
+ ("MulticamSequence", "multiply_colors", "color_multiply"),
+ ("MulticamSequence", "de_interlace", "use_deinterlace"),
+ ("MulticamSequence", "flip_x", "use_flip_x"),
+ ("MulticamSequence", "flip_y", "use_flip_y"),
+ ("MulticamSequence", "convert_float", "use_float"),
+ ("MulticamSequence", "premultiply", "use_premultiply"),
+ ("MulticamSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
+ ("MulticamSequence", "proxy_custom_file", "use_proxy_custom_file"),
+ ("MulticamSequence", "reverse_frames", "use_reverse_frames"),
+ ("SceneSequence", "animation_end_offset", "animation_offset_end"),
+ ("SceneSequence", "animation_start_offset", "animation_offset_start"),
+ ("SceneSequence", "multiply_colors", "color_multiply"),
+ ("SceneSequence", "de_interlace", "use_deinterlace"),
+ ("SceneSequence", "flip_x", "use_flip_x"),
+ ("SceneSequence", "flip_y", "use_flip_y"),
+ ("SceneSequence", "convert_float", "use_float"),
+ ("SceneSequence", "premultiply", "use_premultiply"),
+ ("SceneSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
+ ("SceneSequence", "proxy_custom_file", "use_proxy_custom_file"),
+ ("SceneSequence", "reverse_frames", "use_reverse_frames"),
+ ("SoundSequence", "animation_end_offset", "animation_offset_end"),
+ ("SoundSequence", "animation_start_offset", "animation_offset_start"),
+ ("SmokeDomainSettings", "smoke_domain_colli", "collision_extents"),
+ ("SmokeDomainSettings", "smoke_cache_high_comp", "point_cache_compress_high_type"),
+ ("SmokeDomainSettings", "smoke_cache_comp", "point_cache_compress_type"),
+ ("SmokeDomainSettings", "maxres", "resolution_max"),
+ ("SmokeDomainSettings", "smoothemitter", "smooth_emitter"),
+ ("SmokeDomainSettings", "dissolve_smoke", "use_dissolve_smoke"),
+ ("SmokeDomainSettings", "dissolve_smoke_log", "use_dissolve_smoke_log"),
+ ("SmokeDomainSettings", "highres", "use_high_resolution"),
+ ("SoftBodySettings", "bending", "bend"),
+ ("SoftBodySettings", "error_limit", "error_threshold"),
+ ("SoftBodySettings", "lcom", "location_mass_center"),
+ ("SoftBodySettings", "lrot", "rotation_estimate"),
+ ("SoftBodySettings", "lscale", "scale_estimate"),
+ ("SoftBodySettings", "maxstep", "step_max"),
+ ("SoftBodySettings", "minstep", "step_min"),
+ ("SoftBodySettings", "diagnose", "use_diagnose"),
+ ("SoftBodySettings", "edge_collision", "use_edge_collision"),
+ ("SoftBodySettings", "estimate_matrix", "use_estimate_matrix"),
+ ("SoftBodySettings", "face_collision", "use_face_collision"),
+ ("SoftBodySettings", "self_collision", "use_self_collision"),
+ ("SoftBodySettings", "stiff_quads", "use_stiff_quads"),
+ ("TexMapping", "maximum", "max"),
+ ("TexMapping", "minimum", "min"),
+ ("TexMapping", "has_maximum", "use_max"),
+ ("TexMapping", "has_minimum", "use_min"),
+ ("TextCharacterFormat", "bold", "use_bold"),
+ ("TextCharacterFormat", "italic", "use_italic"),
+ ("TextCharacterFormat", "underline", "use_underline"),
+ ("TextureSlot", "rgb_to_intensity", "use_rgb_to_intensity"),
+ ("TextureSlot", "stencil", "use_stencil"),
+ ("LampTextureSlot", "texture_coordinates", "texture_coords"),
+ ("LampTextureSlot", "map_color", "use_map_color"),
+ ("LampTextureSlot", "map_shadow", "use_map_shadow"),
+ ("MaterialTextureSlot", "coloremission_factor", "color_emission_factor"),
+ ("MaterialTextureSlot", "colordiff_factor", "diffuse_color_factor"),
+ ("MaterialTextureSlot", "x_mapping", "mapping_x"),
+ ("MaterialTextureSlot", "y_mapping", "mapping_y"),
+ ("MaterialTextureSlot", "z_mapping", "mapping_z"),
+ ("MaterialTextureSlot", "colorreflection_factor", "reflection_color_factor"),
+ ("MaterialTextureSlot", "colorspec_factor", "specular_color_factor"),
+ ("MaterialTextureSlot", "texture_coordinates", "texture_coords"),
+ ("MaterialTextureSlot", "colortransmission_factor", "transmission_color_factor"),
+ ("MaterialTextureSlot", "from_dupli", "use_from_dupli"),
+ ("MaterialTextureSlot", "from_original", "use_from_original"),
+ ("MaterialTextureSlot", "map_alpha", "use_map_alpha"),
+ ("MaterialTextureSlot", "map_ambient", "use_map_ambient"),
+ ("MaterialTextureSlot", "map_colordiff", "use_map_color_diff"),
+ ("MaterialTextureSlot", "map_coloremission", "use_map_color_emission"),
+ ("MaterialTextureSlot", "map_colorreflection", "use_map_color_reflection"),
+ ("MaterialTextureSlot", "map_colorspec", "use_map_color_spec"),
+ ("MaterialTextureSlot", "map_colortransmission", "use_map_color_transmission"),
+ ("MaterialTextureSlot", "map_density", "use_map_density"),
+ ("MaterialTextureSlot", "map_diffuse", "use_map_diffuse"),
+ ("MaterialTextureSlot", "map_displacement", "use_map_displacement"),
+ ("MaterialTextureSlot", "map_emission", "use_map_emission"),
+ ("MaterialTextureSlot", "map_emit", "use_map_emit"),
+ ("MaterialTextureSlot", "map_hardness", "use_map_hardness"),
+ ("MaterialTextureSlot", "map_mirror", "use_map_mirror"),
+ ("MaterialTextureSlot", "map_normal", "use_map_normal"),
+ ("MaterialTextureSlot", "map_raymir", "use_map_raymir"),
+ ("MaterialTextureSlot", "map_reflection", "use_map_reflect"),
+ ("MaterialTextureSlot", "map_scattering", "use_map_scatter"),
+ ("MaterialTextureSlot", "map_specular", "use_map_specular"),
+ ("MaterialTextureSlot", "map_translucency", "use_map_translucency"),
+ ("MaterialTextureSlot", "map_warp", "use_map_warp"),
+ ("WorldTextureSlot", "texture_coordinates", "texture_coords"),
+ ("WorldTextureSlot", "map_blend", "use_map_blend"),
+ ("WorldTextureSlot", "map_horizon", "use_map_horizon"),
+ ("WorldTextureSlot", "map_zenith_down", "use_map_zenith_down"),
+ ("WorldTextureSlot", "map_zenith_up", "use_map_zenith_up"),
+ ("VoxelData", "still_frame_number", "still_frame"),
+ ("WorldLighting", "ao_blend_mode", "ao_blend_type"),
+ ("WorldLighting", "error_tolerance", "error_threshold"),
+ ("WorldLighting", "use_ambient_occlusion", "use_ambient_occlusian"),
+ ("WorldLighting", "pixel_cache", "use_cache"),
+ ("WorldLighting", "use_environment_lighting", "use_environment_light"),
+ ("WorldLighting", "use_indirect_lighting", "use_indirect_light"),
+ ("WorldStarsSettings", "color_randomization", "color_random"),
+ ("WorldStarsSettings", "min_distance", "distance_min"),
+ ("WorldLighting", "falloff", "use_falloff"),
+ ("Constraint", "disabled", "is_valid"),
+ ("ClampToConstraint", "cyclic", "use_cyclic"),
+ ("ImageTexture", "filter", "filter_type"),
+ ("ImageTexture", "interpolation", "use_interpolation"),
+ ("ImageTexture", "mipmap", "use_mipmap"),
+ ("ImageUser", "frames", "frame_duration"),
+ ("ImageUser", "offset", "frame_offset"),
+ ("ImageUser", "cyclic", "use_cyclic"),
+ ("ArmatureModifier", "invert", "invert_vertex_group"),
+ ("ArmatureModifier", "quaternion", "use_deform_preserve_volume"),
+ ("ArrayModifier", "length", "fit_length"),
+ ("BevelModifier", "angle", "angle_limit"),
+ ("BuildModifier", "length", "frame_duration"),
+ ("BuildModifier", "randomize", "use_random_order"),
+ ("CastModifier", "x", "use_x"),
+ ("CastModifier", "y", "use_y"),
+ ("CastModifier", "z", "use_z"),
+ ("ExplodeModifier", "size", "use_size"),
+ ("MaskModifier", "invert", "invert_vertex_group"),
+ ("MeshDeformModifier", "invert", "invert_vertex_group"),
+ ("MeshDeformModifier", "dynamic", "use_dynamic_bind"),
+ ("MirrorModifier", "clip", "use_clip"),
+ ("MirrorModifier", "x", "use_x"),
+ ("MirrorModifier", "y", "use_y"),
+ ("MirrorModifier", "z", "use_z"),
+ ("ParticleInstanceModifier", "children", "use_children"),
+ ("ParticleInstanceModifier", "normal", "use_normal"),
+ ("ParticleInstanceModifier", "size", "use_size"),
+ ("ShrinkwrapModifier", "negative", "use_negative_direction"),
+ ("ShrinkwrapModifier", "positive", "use_positive_direction"),
+ ("ShrinkwrapModifier", "x", "use_project_x"),
+ ("ShrinkwrapModifier", "y", "use_project_y"),
+ ("ShrinkwrapModifier", "z", "use_project_z"),
+ ("ShrinkwrapModifier", "mode", "wrap_method"),
+ ("SimpleDeformModifier", "mode", "deform_method"),
+ ("SimpleDeformModifier", "relative", "use_relative"),
+ ("SmoothModifier", "repeat", "iterations"),
+ ("SmoothModifier", "x", "use_x"),
+ ("SmoothModifier", "y", "use_y"),
+ ("SmoothModifier", "z", "use_z"),
+ ("SolidifyModifier", "invert", "invert_vertex_group"),
+ ("WaveModifier", "cyclic", "use_cyclic"),
+ ("WaveModifier", "normals", "use_normal"),
+ ("WaveModifier", "x", "use_x"),
+ ("WaveModifier", "y", "use_y"),
+ ("DampedTrackConstraint", "track", "track_axis"),
+ ("FloorConstraint", "sticky", "use_sticky"),
+ ("FollowPathConstraint", "forward", "forward_axis"),
+ ("FollowPathConstraint", "up", "up_axis"),
+ ("LockedTrackConstraint", "lock", "lock_axis"),
+ ("LockedTrackConstraint", "track", "track_axis"),
+ ("MaintainVolumeConstraint", "axis", "free_axis"),
+ ("TrackToConstraint", "track", "track_axis"),
+ ("TrackToConstraint", "up", "up_axis"),
+ ("GameProperty", "debug", "show_debug"),
+ ("Image", "tiles", "use_tiles"),
+ ("Lamp", "diffuse", "use_diffuse"),
+ ("Lamp", "negative", "use_negative"),
+ ("Lamp", "layer", "use_own_layer"),
+ ("Lamp", "specular", "use_specular"),
+ ("AreaLamp", "dither", "use_dither"),
+ ("AreaLamp", "jitter", "use_jitter"),
+ ("SpotLamp", "square", "use_square"),
+ ("Material", "cubic", "use_cubic"),
+ ("Material", "shadows", "use_shadows"),
+ ("ParticleSettings", "amount", "count"),
+ ("ParticleSettings", "display", "draw_percentage"),
+ ("ParticleSettings", "velocity", "show_velocity"),
+ ("ParticleSettings", "trand", "use_emit_random"),
+ ("ParticleSettings", "parent", "use_parent_particles"),
+ ("ParticleSettings", "emitter", "use_render_emitter"),
+ ("ParticleSettings", "viewport", "use_simplify_viewport"),
+ ("Texture", "brightness", "intensity"),
+ ("CloudsTexture", "stype", "cloud_type"),
+ ("EnvironmentMapTexture", "filter", "filter_type"),
+ ("EnvironmentMapTexture", "mipmap", "use_mipmap"),
+ ("MarbleTexture", "stype", "marble_type"),
+ ("StucciTexture", "stype", "stucci_type"),
+ ("WoodTexture", "stype", "wood_type"),
+ ("World", "range", "color_range"),
+ ("World", "lighting", "light_settings"),
+ ("World", "mist", "mist_settings"),
+ ("World", "stars", "star_settings"),
+ ("MaterialHalo", "lines", "use_lines"),
+ ("MaterialHalo", "ring", "use_ring"),
+ ("MaterialHalo", "soft", "use_soft"),
+ ("MaterialHalo", "star", "use_star"),
+ ("MaterialHalo", "texture", "use_texture"),
+ ("MaterialPhysics", "damp", "damping"),
+ ("MaterialRaytraceTransparency", "limit", "depth_max"),
+ ("NlaStrip", "reversed", "use_reverse"),
+ ("CompositorNodeBlur", "bokeh", "use_bokeh"),
+ ("CompositorNodeBlur", "gamma", "use_gamma_correction"),
+ ("CompositorNodeBlur", "relative", "use_relative"),
+ ("CompositorNodeChannelMatte", "high", "limit_max"),
+ ("CompositorNodeChannelMatte", "low", "limit_min"),
+ ("CompositorNodeChannelMatte", "channel", "matte_channel"),
+ ("CompositorNodeChromaMatte", "cutoff", "threshold"),
+ ("CompositorNodeColorMatte", "h", "color_hue"),
+ ("CompositorNodeColorMatte", "s", "color_saturation"),
+ ("CompositorNodeColorMatte", "v", "color_value"),
+ ("CompositorNodeDBlur", "wrap", "use_wrap"),
+ ("CompositorNodeDefocus", "preview", "use_preview"),
+ ("CompositorNodeHueSat", "hue", "color_hue"),
+ ("CompositorNodeHueSat", "sat", "color_saturation"),
+ ("CompositorNodeHueSat", "val", "color_value"),
+ ("CompositorNodeImage", "frames", "frame_duration"),
+ ("CompositorNodeImage", "offset", "frame_offset"),
+ ("CompositorNodeImage", "start", "frame_start"),
+ ("CompositorNodeImage", "cyclic", "use_cyclic"),
+ ("CompositorNodeInvert", "alpha", "invert_alpha"),
+ ("CompositorNodeInvert", "rgb", "invert_rgb"),
+ ("CompositorNodeLensdist", "fit", "use_fit"),
+ ("CompositorNodeLensdist", "jitter", "use_jitter"),
+ ("CompositorNodeMixRGB", "alpha", "use_alpha"),
+ ("CompositorNodeRotate", "filter", "filter_type"),
+ ("CompositorNodeTime", "end", "frame_end"),
+ ("CompositorNodeTime", "start", "frame_start"),
+ ("CompositorNodeVecBlur", "curved", "use_curved"),
+ ("ShaderNodeExtendedMaterial", "diffuse", "use_diffuse"),
+ ("ShaderNodeExtendedMaterial", "specular", "use_specular"),
+ ("ShaderNodeMaterial", "diffuse", "use_diffuse"),
+ ("ShaderNodeMaterial", "specular", "use_specular"),
+ ("ShaderNodeMixRGB", "alpha", "use_alpha"),
+ ("TextureNodeCurveTime", "end", "frame_end"),
+ ("TextureNodeCurveTime", "start", "frame_start"),
+ ("TextureNodeMixRGB", "alpha", "use_alpha"),
+ ("TextureSlot", "negate", "invert"),
+ ("TextureSlot", "size", "scale"),
+ ("SoftBodySettings", "damp", "damping"),
+ ("SequenceCrop", "right", "max_x"),
+ ("SequenceCrop", "top", "max_y"),
+ ("SequenceCrop", "bottom", "min_x"),
+ ("SequenceCrop", "left", "min_y"),
+ ("Sequence", "speed_fader", "speed_factor"),
+ ("SpeedControlSequence", "global_speed", "multiply_speed"),
+ ("SpeedControlSequence", "use_curve_velocity", "use_as_speed"),
+ ("SpeedControlSequence", "use_curve_compress_y", "scale_to_length"),
+ ]
+
+
+import bpy
+
+
+class UpdateAnimData(bpy.types.Operator):
+ '''Update data paths from 2.53 to edited data paths of drivers and fcurves'''
+ bl_idname = "anim.update_data_paths"
+ bl_label = "Update Animation Data"
+
+ def execute(self, context):
+ import animsys_refactor
+ animsys_refactor.update_data_paths(data_path_update)
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/fcurve_euler_filter.py b/release/scripts/startup/bl_operators/fcurve_euler_filter.py
new file mode 100644
index 00000000000..c7b249a1d0c
--- /dev/null
+++ b/release/scripts/startup/bl_operators/fcurve_euler_filter.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 #####
+
+# <pep8 compliant>
+
+import bpy
+
+
+def main(context):
+ from math import pi
+
+ def cleanupEulCurve(fcv):
+ keys = []
+
+ for k in fcv.keyframe_points:
+ keys.append([k.handle_left.copy(), k.co.copy(), k.handle_right.copy()])
+
+ for i in range(len(keys)):
+ cur = keys[i]
+ prev = keys[i - 1] if i > 0 else None
+ next = keys[i + 1] if i < len(keys) - 1 else None
+
+ if prev is None:
+ continue
+
+ th = pi
+ if abs(prev[1][1] - cur[1][1]) >= th: # more than 180 degree jump
+ fac = pi * 2.0
+ if prev[1][1] > cur[1][1]:
+ while abs(cur[1][1] - prev[1][1]) >= th: # < prev[1][1]:
+ cur[0][1] += fac
+ cur[1][1] += fac
+ cur[2][1] += fac
+ elif prev[1][1] < cur[1][1]:
+ while abs(cur[1][1] - prev[1][1]) >= th:
+ cur[0][1] -= fac
+ cur[1][1] -= fac
+ cur[2][1] -= fac
+
+ for i in range(len(keys)):
+ for x in range(2):
+ fcv.keyframe_points[i].handle_left[x] = keys[i][0][x]
+ fcv.keyframe_points[i].co[x] = keys[i][1][x]
+ fcv.keyframe_points[i].handle_right[x] = keys[i][2][x]
+
+ flist = bpy.context.active_object.animation_data.action.fcurves
+ for f in flist:
+ if f.select and f.data_path.endswith("rotation_euler"):
+ cleanupEulCurve(f)
+
+
+class DiscontFilterOp(bpy.types.Operator):
+ """Fixes the most common causes of gimbal lock in the fcurves of the active bone"""
+ bl_idname = "graph.euler_filter"
+ bl_label = "Filter out discontinuities in the active fcurves"
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ main(context)
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/image.py b/release/scripts/startup/bl_operators/image.py
new file mode 100644
index 00000000000..82e631e31d2
--- /dev/null
+++ b/release/scripts/startup/bl_operators/image.py
@@ -0,0 +1,189 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+from bpy.props import StringProperty
+
+
+class EditExternally(bpy.types.Operator):
+ '''Edit image in an external application'''
+ bl_idname = "image.external_edit"
+ bl_label = "Image Edit Externally"
+ bl_options = {'REGISTER'}
+
+ filepath = StringProperty(name="File Path", description="Path to an image file", maxlen=1024, default="")
+
+ def _editor_guess(self, context):
+ import sys
+
+ image_editor = context.user_preferences.filepaths.image_editor
+
+ # use image editor in the preferences when available.
+ if not image_editor:
+ if sys.platform[:3] == "win":
+ image_editor = ["start"] # not tested!
+ elif sys.platform == "darwin":
+ image_editor = ["open"]
+ else:
+ image_editor = ["gimp"]
+ else:
+ if sys.platform == "darwin":
+ # blender file selector treats .app as a folder
+ # and will include a trailing backslash, so we strip it.
+ image_editor.rstrip('\\')
+ image_editor = ["open", "-a", image_editor]
+ else:
+ image_editor = [image_editor]
+
+ return image_editor
+
+ def execute(self, context):
+ import os
+ import subprocess
+ filepath = bpy.path.abspath(self.filepath)
+
+ if not os.path.exists(filepath):
+ self.report('ERROR', "Image path %r not found." % filepath)
+ return {'CANCELLED'}
+
+ cmd = self._editor_guess(context) + [filepath]
+
+ subprocess.Popen(cmd)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ try:
+ filepath = context.space_data.image.filepath
+ except:
+ self.report({'ERROR'}, "Image not found on disk")
+ return {'CANCELLED'}
+
+ self.filepath = filepath
+ self.execute(context)
+
+ return {'FINISHED'}
+
+
+class SaveDirty(bpy.types.Operator):
+ """Save all modified textures"""
+ bl_idname = "image.save_dirty"
+ bl_label = "Save Dirty"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ unique_paths = set()
+ for image in bpy.data.images:
+ if image.is_dirty:
+ filepath = bpy.path.abspath(image.filepath)
+ if "\\" not in filepath and "/" not in filepath:
+ self.report({'WARNING'}, "Invalid path: " + filepath)
+ elif filepath in unique_paths:
+ self.report({'WARNING'}, "Path used by more then one image: " + filepath)
+ else:
+ unique_paths.add(filepath)
+ image.save()
+ return {'FINISHED'}
+
+
+class ProjectEdit(bpy.types.Operator):
+ """Edit a snapshot if the viewport in an external image editor"""
+ bl_idname = "image.project_edit"
+ bl_label = "Project Edit"
+ bl_options = {'REGISTER'}
+
+ _proj_hack = [""]
+
+ def execute(self, context):
+ import os
+ import subprocess
+
+ EXT = "png" # could be made an option but for now ok
+
+ for image in bpy.data.images:
+ image.tag = True
+
+ if 'FINISHED' not in bpy.ops.paint.image_from_view():
+ return {'CANCELLED'}
+
+ image_new = None
+ for image in bpy.data.images:
+ if not image.tag:
+ image_new = image
+ break
+
+ if not image_new:
+ self.report({'ERROR'}, "Could not make new image")
+ return {'CANCELLED'}
+
+ filepath = os.path.basename(bpy.data.filepath)
+ filepath = os.path.splitext(filepath)[0]
+ # filepath = bpy.path.clean_name(filepath) # fixes <memory> rubbish, needs checking
+
+ if filepath.startswith(".") or filepath == "":
+ # TODO, have a way to check if the file is saved, assume startup.blend
+ tmpdir = context.user_preferences.filepaths.temporary_directory
+ filepath = os.path.join(tmpdir, "project_edit")
+ else:
+ filepath = "//" + filepath
+
+ obj = context.object
+
+ if obj:
+ filepath += "_" + bpy.path.clean_name(obj.name)
+
+ filepath_final = filepath + "." + EXT
+ i = 0
+
+ while os.path.exists(bpy.path.abspath(filepath_final)):
+ filepath_final = filepath + ("%.3d.%s" % (i, EXT))
+ i += 1
+
+ image_new.name = os.path.basename(filepath_final)
+ ProjectEdit._proj_hack[0] = image_new.name
+
+ image_new.filepath_raw = filepath_final # TODO, filepath raw is crummy
+ image_new.file_format = 'PNG'
+ image_new.save()
+
+ bpy.ops.image.external_edit(filepath=filepath_final)
+
+ return {'FINISHED'}
+
+
+class ProjectApply(bpy.types.Operator):
+ """Project edited image back onto the object"""
+ bl_idname = "image.project_apply"
+ bl_label = "Project Apply"
+ bl_options = {'REGISTER'}
+
+ def execute(self, context):
+ image_name = ProjectEdit._proj_hack[0] # TODO, deal with this nicer
+
+ try:
+ image = bpy.data.images[image_name]
+ except KeyError:
+ self.report({'ERROR'}, "Could not find image '%s'" % image_name)
+ return {'CANCELLED'}
+
+ image.reload()
+ bpy.ops.paint.project_image(image=image_name)
+
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/mesh.py b/release/scripts/startup/bl_operators/mesh.py
new file mode 100644
index 00000000000..a37a83f0f09
--- /dev/null
+++ b/release/scripts/startup/bl_operators/mesh.py
@@ -0,0 +1,172 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+
+
+class MeshSelectInteriorFaces(bpy.types.Operator):
+ '''Select faces where all edges have more then 2 face users.'''
+
+ bl_idname = "mesh.faces_select_interior"
+ bl_label = "Select Interior Faces"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.active_object
+ return (ob and ob.type == 'MESH')
+
+ def execute(self, context):
+ ob = context.active_object
+ context.tool_settings.mesh_select_mode = False, False, True
+ is_editmode = (ob.mode == 'EDIT')
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+
+ mesh = ob.data
+
+ face_list = mesh.faces[:]
+ face_edge_keys = [face.edge_keys for face in face_list]
+
+ edge_face_count = mesh.edge_face_count_dict
+
+ def test_interior(index):
+ for key in face_edge_keys[index]:
+ if edge_face_count[key] < 3:
+ return False
+ return True
+
+ for index, face in enumerate(face_list):
+ if(test_interior(index)):
+ face.select = True
+ else:
+ face.select = False
+
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+ return {'FINISHED'}
+
+
+class MeshMirrorUV(bpy.types.Operator):
+ '''Copy mirror UV coordinates on the X axis based on a mirrored mesh'''
+ bl_idname = "mesh.faces_miror_uv"
+ bl_label = "Copy Mirrored UV coords"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.active_object
+ return (ob and ob.type == 'MESH')
+
+ def execute(self, context):
+ DIR = 1 # TODO, make an option
+
+ from mathutils import Vector
+
+ ob = context.active_object
+ is_editmode = (ob.mode == 'EDIT')
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+
+ mesh = ob.data
+
+ # mirror lookups
+ mirror_gt = {}
+ mirror_lt = {}
+
+ vcos = [v.co.to_tuple(5) for v in mesh.vertices]
+
+ for i, co in enumerate(vcos):
+ if co[0] > 0.0:
+ mirror_gt[co] = i
+ elif co[0] < 0.0:
+ mirror_lt[co] = i
+ else:
+ mirror_gt[co] = i
+ mirror_lt[co] = i
+
+ #for i, v in enumerate(mesh.vertices):
+ vmap = {}
+ for mirror_a, mirror_b in (mirror_gt, mirror_lt), (mirror_lt, mirror_gt):
+ for co, i in mirror_a.items():
+ nco = (-co[0], co[1], co[2])
+ j = mirror_b.get(nco)
+ if j is not None:
+ vmap[i] = j
+
+ active_uv_layer = None
+ for lay in mesh.uv_textures:
+ if lay.active:
+ active_uv_layer = lay.data
+ break
+
+ fuvs = [(uv.uv1, uv.uv2, uv.uv3, uv.uv4) for uv in active_uv_layer]
+ fuvs_cpy = [(uv[0].copy(), uv[1].copy(), uv[2].copy(), uv[3].copy()) for uv in fuvs]
+
+ # as a list
+ faces = mesh.faces[:]
+
+ fuvsel = [(False not in uv.select_uv) for uv in active_uv_layer]
+ fcents = [f.center for f in faces]
+
+ # find mirror faces
+ mirror_fm = {}
+ for i, f in enumerate(faces):
+ verts = list(f.vertices)
+ verts.sort()
+ verts = tuple(verts)
+ mirror_fm[verts] = i
+
+ fmap = {}
+ for i, f in enumerate(faces):
+ verts = [vmap.get(j) for j in f.vertices]
+ if None not in verts:
+ verts.sort()
+ j = mirror_fm.get(tuple(verts))
+ if j is not None:
+ fmap[i] = j
+
+ done = [False] * len(faces)
+ for i, j in fmap.items():
+
+ if not fuvsel[i] or not fuvsel[j]:
+ continue
+ elif DIR == 0 and fcents[i][0] < 0.0:
+ continue
+ elif DIR == 1 and fcents[i][0] > 0.0:
+ continue
+
+ # copy UVs
+ uv1 = fuvs[i]
+ uv2 = fuvs_cpy[j]
+
+ # get the correct rotation
+ v1 = faces[j].vertices[:]
+ v2 = [vmap[k] for k in faces[i].vertices[:]]
+
+ for k in range(len(uv1)):
+ k_map = v1.index(v2[k])
+ uv1[k].x = - (uv2[k_map].x - 0.5) + 0.5
+ uv1[k].y = uv2[k_map].y
+
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/nla.py b/release/scripts/startup/bl_operators/nla.py
new file mode 100644
index 00000000000..923ca92a162
--- /dev/null
+++ b/release/scripts/startup/bl_operators/nla.py
@@ -0,0 +1,170 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+
+
+def pose_info():
+ from mathutils import Matrix
+
+ info = {}
+
+ obj = bpy.context.object
+ pose = obj.pose
+
+ pose_items = pose.bones.items()
+
+ for name, pbone in pose_items:
+ binfo = {}
+ bone = pbone.bone
+
+ binfo["parent"] = getattr(bone.parent, "name", None)
+ binfo["bone"] = bone
+ binfo["pbone"] = pbone
+ binfo["matrix_local"] = bone.matrix_local.copy()
+ try:
+ binfo["matrix_local_inv"] = binfo["matrix_local"].inverted()
+ except:
+ binfo["matrix_local_inv"] = Matrix()
+
+ binfo["matrix"] = bone.matrix.copy()
+ binfo["matrix_pose"] = pbone.matrix.copy()
+ try:
+ binfo["matrix_pose_inv"] = binfo["matrix_pose"].inverted()
+ except:
+ binfo["matrix_pose_inv"] = Matrix()
+
+ print(binfo["matrix_pose"])
+ info[name] = binfo
+
+ for name, pbone in pose_items:
+ binfo = info[name]
+ binfo_parent = binfo.get("parent", None)
+ if binfo_parent:
+ binfo_parent = info[binfo_parent]
+
+ matrix = binfo["matrix_pose"]
+ rest_matrix = binfo["matrix_local"]
+
+ if binfo_parent:
+ matrix = binfo_parent["matrix_pose_inv"] * matrix
+ rest_matrix = binfo_parent["matrix_local_inv"] * rest_matrix
+
+ matrix = rest_matrix.inverted() * matrix
+
+ binfo["matrix_key"] = matrix.copy()
+
+ return info
+
+
+def bake(frame_start, frame_end, step=1, only_selected=False):
+ scene = bpy.context.scene
+ obj = bpy.context.object
+ pose = obj.pose
+
+ info_ls = []
+
+ frame_range = range(frame_start, frame_end + 1, step)
+
+ # could spped this up by applying steps here too...
+ for f in frame_range:
+ scene.frame_set(f)
+
+ info = pose_info()
+ info_ls.append(info)
+ f += 1
+
+ action = bpy.data.actions.new("Action")
+
+ bpy.context.object.animation_data.action = action
+
+ pose_items = pose.bones.items()
+
+ for name, pbone in pose_items:
+ if only_selected and not pbone.bone.select:
+ continue
+
+ for f in frame_range:
+ matrix = info_ls[int((f - frame_start) / step)][name]["matrix_key"]
+
+ #pbone.location = matrix.to_translation()
+ #pbone.rotation_quaternion = matrix.to_quaternion()
+ pbone.matrix_basis = matrix
+
+ pbone.keyframe_insert("location", -1, f, name)
+
+ rotation_mode = pbone.rotation_mode
+
+ if rotation_mode == 'QUATERNION':
+ pbone.keyframe_insert("rotation_quaternion", -1, f, name)
+ elif rotation_mode == 'AXIS_ANGLE':
+ pbone.keyframe_insert("rotation_axis_angle", -1, f, name)
+ else: # euler, XYZ, ZXY etc
+ pbone.keyframe_insert("rotation_euler", -1, f, name)
+
+ pbone.keyframe_insert("scale", -1, f, name)
+
+ return action
+
+
+from bpy.props import IntProperty, BoolProperty
+
+
+class BakeAction(bpy.types.Operator):
+ '''Bake animation to an Action'''
+ bl_idname = "nla.bake"
+ bl_label = "Bake Action"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ frame_start = IntProperty(name="Start Frame",
+ description="Start frame for baking",
+ default=1, min=1, max=300000)
+ frame_end = IntProperty(name="End Frame",
+ description="End frame for baking",
+ default=250, min=1, max=300000)
+ step = IntProperty(name="Frame Step",
+ description="Frame Step",
+ default=1, min=1, max=120)
+ only_selected = BoolProperty(name="Only Selected",
+ default=True)
+
+ def execute(self, context):
+
+ action = bake(self.frame_start, self.frame_end, self.step, self.only_selected)
+
+ # basic cleanup, could move elsewhere
+ for fcu in action.fcurves:
+ keyframe_points = fcu.keyframe_points
+ i = 1
+ while i < len(fcu.keyframe_points) - 1:
+ val_prev = keyframe_points[i - 1].co[1]
+ val_next = keyframe_points[i + 1].co[1]
+ val = keyframe_points[i].co[1]
+
+ if abs(val - val_prev) + abs(val - val_next) < 0.0001:
+ keyframe_points.remove(keyframe_points[i])
+ else:
+ i += 1
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self)
diff --git a/release/scripts/startup/bl_operators/object.py b/release/scripts/startup/bl_operators/object.py
new file mode 100644
index 00000000000..db5eaccfc8f
--- /dev/null
+++ b/release/scripts/startup/bl_operators/object.py
@@ -0,0 +1,563 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty
+
+
+class SelectPattern(bpy.types.Operator):
+ '''Select object matching a naming pattern'''
+ bl_idname = "object.select_pattern"
+ bl_label = "Select Pattern"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ pattern = StringProperty(name="Pattern", description="Name filter using '*' and '?' wildcard chars", maxlen=32, default="*")
+ case_sensitive = BoolProperty(name="Case Sensitive", description="Do a case sensitive compare", default=False)
+ extend = BoolProperty(name="Extend", description="Extend the existing selection", default=True)
+
+ def execute(self, context):
+
+ import fnmatch
+
+ if self.case_sensitive:
+ pattern_match = fnmatch.fnmatchcase
+ else:
+ pattern_match = lambda a, b: fnmatch.fnmatchcase(a.upper(), b.upper())
+
+ obj = context.object
+ if obj and obj.mode == 'POSE':
+ items = obj.data.bones
+ elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT':
+ items = obj.data.edit_bones
+ else:
+ items = context.visible_objects
+
+ # Can be pose bones or objects
+ for item in items:
+ if pattern_match(item.name, self.pattern):
+ item.select = True
+ elif not self.extend:
+ item.select = False
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ return wm.invoke_props_popup(self, event)
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.prop(self, "pattern")
+ row = layout.row()
+ row.prop(self, "case_sensitive")
+ row.prop(self, "extend")
+
+
+class SelectCamera(bpy.types.Operator):
+ '''Select object matching a naming pattern'''
+ bl_idname = "object.select_camera"
+ bl_label = "Select Camera"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return context.scene.camera is not None
+
+ def execute(self, context):
+ scene = context.scene
+ camera = scene.camera
+ if camera.name not in scene.objects:
+ self.report({'WARNING'}, "Active camera is not in this scene")
+
+ context.scene.objects.active = camera
+ camera.select = True
+ return {'FINISHED'}
+
+
+class SelectHierarchy(bpy.types.Operator):
+ '''Select object relative to the active objects position in the hierarchy'''
+ bl_idname = "object.select_hierarchy"
+ bl_label = "Select Hierarchy"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ direction = EnumProperty(items=(
+ ('PARENT', "Parent", ""),
+ ('CHILD', "Child", "")),
+ name="Direction",
+ description="Direction to select in the hierarchy",
+ default='PARENT')
+
+ extend = BoolProperty(name="Extend", description="Extend the existing selection", default=False)
+
+ @classmethod
+ def poll(cls, context):
+ return context.object
+
+ def execute(self, context):
+ select_new = []
+ act_new = None
+
+ selected_objects = context.selected_objects
+ obj_act = context.object
+
+ if context.object not in selected_objects:
+ selected_objects.append(context.object)
+
+ if self.direction == 'PARENT':
+ for obj in selected_objects:
+ parent = obj.parent
+
+ if parent:
+ if obj_act == obj:
+ act_new = parent
+
+ select_new.append(parent)
+
+ else:
+ for obj in selected_objects:
+ select_new.extend(obj.children)
+
+ if select_new:
+ select_new.sort(key=lambda obj_iter: obj_iter.name)
+ act_new = select_new[0]
+
+ # dont edit any object settings above this
+ if select_new:
+ if not self.extend:
+ bpy.ops.object.select_all(action='DESELECT')
+
+ for obj in select_new:
+ obj.select = True
+
+ context.scene.objects.active = act_new
+ return {'FINISHED'}
+
+ return {'CANCELLED'}
+
+
+class SubdivisionSet(bpy.types.Operator):
+ '''Sets a Subdivision Surface Level (1-5)'''
+
+ bl_idname = "object.subdivision_set"
+ bl_label = "Subdivision Set"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ level = IntProperty(name="Level",
+ default=1, min=-100, max=100, soft_min=-6, soft_max=6)
+
+ relative = BoolProperty(name="Relative", description="Apply the subsurf level as an offset relative to the current level", default=False)
+
+ @classmethod
+ def poll(cls, context):
+ obs = context.selected_editable_objects
+ return (obs is not None)
+
+ def execute(self, context):
+ level = self.level
+ relative = self.relative
+
+ if relative and level == 0:
+ return {'CANCELLED'} # nothing to do
+
+ def set_object_subd(obj):
+ for mod in obj.modifiers:
+ if mod.type == 'MULTIRES':
+ if not relative:
+ if level <= mod.total_levels:
+ if obj.mode == 'SCULPT':
+ if mod.sculpt_levels != level:
+ mod.sculpt_levels = level
+ elif obj.mode == 'OBJECT':
+ if mod.levels != level:
+ mod.levels = level
+ return
+ else:
+ if obj.mode == 'SCULPT':
+ if mod.sculpt_levels + level <= mod.total_levels:
+ mod.sculpt_levels += level
+ elif obj.mode == 'OBJECT':
+ if mod.levels + level <= mod.total_levels:
+ mod.levels += level
+ return
+
+ elif mod.type == 'SUBSURF':
+ if relative:
+ mod.levels += level
+ else:
+ if mod.levels != level:
+ mod.levels = level
+
+ return
+
+ # add a new modifier
+ try:
+ mod = obj.modifiers.new("Subsurf", 'SUBSURF')
+ mod.levels = level
+ except:
+ self.report({'WARNING'}, "Modifiers cannot be added to object: " + obj.name)
+
+ for obj in context.selected_editable_objects:
+ set_object_subd(obj)
+
+ return {'FINISHED'}
+
+
+class ShapeTransfer(bpy.types.Operator):
+ '''Copy another selected objects active shape to this one by applying the relative offsets'''
+
+ bl_idname = "object.shape_key_transfer"
+ bl_label = "Transfer Shape Key"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ mode = EnumProperty(items=(
+ ('OFFSET', "Offset", "Apply the relative positional offset"),
+ ('RELATIVE_FACE', "Relative Face", "Calculate the geometricly relative position (using faces)."),
+ ('RELATIVE_EDGE', "Relative Edge", "Calculate the geometricly relative position (using edges).")),
+ name="Transformation Mode",
+ description="Method to apply relative shape positions to the new shape",
+ default='OFFSET')
+
+ use_clamp = BoolProperty(name="Clamp Offset",
+ description="Clamp the transformation to the distance each vertex moves in the original shape.",
+ default=False)
+
+ def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
+
+ def me_nos(verts):
+ return [v.normal.copy() for v in verts]
+
+ def me_cos(verts):
+ return [v.co.copy() for v in verts]
+
+ def ob_add_shape(ob, name):
+ me = ob.data
+ key = ob.shape_key_add(from_mix=False)
+ if len(me.shape_keys.keys) == 1:
+ key.name = "Basis"
+ key = ob.shape_key_add(from_mix=False) # we need a rest
+ key.name = name
+ ob.active_shape_key_index = len(me.shape_keys.keys) - 1
+ ob.show_only_shape_key = True
+
+ from mathutils.geometry import barycentric_transform
+ from mathutils import Vector
+
+ if use_clamp and mode == 'OFFSET':
+ use_clamp = False
+
+ me = ob_act.data
+ orig_key_name = ob_act.active_shape_key.name
+
+ orig_shape_coords = me_cos(ob_act.active_shape_key.data)
+
+ orig_normals = me_nos(me.vertices)
+ # orig_coords = me_cos(me.vertices) # the actual mverts location isnt as relyable as the base shape :S
+ orig_coords = me_cos(me.shape_keys.keys[0].data)
+
+ for ob_other in objects:
+ me_other = ob_other.data
+ if len(me_other.vertices) != len(me.vertices):
+ self.report({'WARNING'}, "Skipping '%s', vertex count differs" % ob_other.name)
+ continue
+
+ target_normals = me_nos(me_other.vertices)
+ if me_other.shape_keys:
+ target_coords = me_cos(me_other.shape_keys.keys[0].data)
+ else:
+ target_coords = me_cos(me_other.vertices)
+
+ ob_add_shape(ob_other, orig_key_name)
+
+ # editing the final coords, only list that stores wrapped coords
+ target_shape_coords = [v.co for v in ob_other.active_shape_key.data]
+
+ median_coords = [[] for i in range(len(me.vertices))]
+
+ # Method 1, edge
+ if mode == 'OFFSET':
+ for i, vert_cos in enumerate(median_coords):
+ vert_cos.append(target_coords[i] + (orig_shape_coords[i] - orig_coords[i]))
+
+ elif mode == 'RELATIVE_FACE':
+ for face in me.faces:
+ i1, i2, i3, i4 = face.vertices_raw
+ if i4 != 0:
+ pt = barycentric_transform(orig_shape_coords[i1],
+ orig_coords[i4], orig_coords[i1], orig_coords[i2],
+ target_coords[i4], target_coords[i1], target_coords[i2])
+ median_coords[i1].append(pt)
+
+ pt = barycentric_transform(orig_shape_coords[i2],
+ orig_coords[i1], orig_coords[i2], orig_coords[i3],
+ target_coords[i1], target_coords[i2], target_coords[i3])
+ median_coords[i2].append(pt)
+
+ pt = barycentric_transform(orig_shape_coords[i3],
+ orig_coords[i2], orig_coords[i3], orig_coords[i4],
+ target_coords[i2], target_coords[i3], target_coords[i4])
+ median_coords[i3].append(pt)
+
+ pt = barycentric_transform(orig_shape_coords[i4],
+ orig_coords[i3], orig_coords[i4], orig_coords[i1],
+ target_coords[i3], target_coords[i4], target_coords[i1])
+ median_coords[i4].append(pt)
+
+ else:
+ pt = barycentric_transform(orig_shape_coords[i1],
+ orig_coords[i3], orig_coords[i1], orig_coords[i2],
+ target_coords[i3], target_coords[i1], target_coords[i2])
+ median_coords[i1].append(pt)
+
+ pt = barycentric_transform(orig_shape_coords[i2],
+ orig_coords[i1], orig_coords[i2], orig_coords[i3],
+ target_coords[i1], target_coords[i2], target_coords[i3])
+ median_coords[i2].append(pt)
+
+ pt = barycentric_transform(orig_shape_coords[i3],
+ orig_coords[i2], orig_coords[i3], orig_coords[i1],
+ target_coords[i2], target_coords[i3], target_coords[i1])
+ median_coords[i3].append(pt)
+
+ elif mode == 'RELATIVE_EDGE':
+ for ed in me.edges:
+ i1, i2 = ed.vertices
+ v1, v2 = orig_coords[i1], orig_coords[i2]
+ edge_length = (v1 - v2).length
+ n1loc = v1 + orig_normals[i1] * edge_length
+ n2loc = v2 + orig_normals[i2] * edge_length
+
+ # now get the target nloc's
+ v1_to, v2_to = target_coords[i1], target_coords[i2]
+ edlen_to = (v1_to - v2_to).length
+ n1loc_to = v1_to + target_normals[i1] * edlen_to
+ n2loc_to = v2_to + target_normals[i2] * edlen_to
+
+ pt = barycentric_transform(orig_shape_coords[i1],
+ v2, v1, n1loc,
+ v2_to, v1_to, n1loc_to)
+ median_coords[i1].append(pt)
+
+ pt = barycentric_transform(orig_shape_coords[i2],
+ v1, v2, n2loc,
+ v1_to, v2_to, n2loc_to)
+ median_coords[i2].append(pt)
+
+ # apply the offsets to the new shape
+ from functools import reduce
+ VectorAdd = Vector.__add__
+
+ for i, vert_cos in enumerate(median_coords):
+ if vert_cos:
+ co = reduce(VectorAdd, vert_cos) / len(vert_cos)
+
+ if use_clamp:
+ # clamp to the same movement as the original
+ # breaks copy between different scaled meshes.
+ len_from = (orig_shape_coords[i] - orig_coords[i]).length
+ ofs = co - target_coords[i]
+ ofs.length = len_from
+ co = target_coords[i] + ofs
+
+ target_shape_coords[i][:] = co
+
+ return {'FINISHED'}
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return (obj and obj.mode != 'EDIT')
+
+ def execute(self, context):
+ C = bpy.context
+ ob_act = C.active_object
+ objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
+
+ if 1: # swap from/to, means we cant copy to many at once.
+ if len(objects) != 1:
+ self.report({'ERROR'}, "Expected one other selected mesh object to copy from")
+ return {'CANCELLED'}
+ ob_act, objects = objects[0], [ob_act]
+
+ if ob_act.type != 'MESH':
+ self.report({'ERROR'}, "Other object is not a mesh.")
+ return {'CANCELLED'}
+
+ if ob_act.active_shape_key is None:
+ self.report({'ERROR'}, "Other object has no shape key")
+ return {'CANCELLED'}
+ return self._main(ob_act, objects, self.mode, self.use_clamp)
+
+
+class JoinUVs(bpy.types.Operator):
+ '''Copy UV Layout to objects with matching geometry'''
+ bl_idname = "object.join_uvs"
+ bl_label = "Join as UVs"
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return (obj and obj.type == 'MESH')
+
+ def _main(self, context):
+ import array
+ obj = context.active_object
+ mesh = obj.data
+
+ is_editmode = (obj.mode == 'EDIT')
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+
+ if not mesh.uv_textures:
+ self.report({'WARNING'}, "Object: %s, Mesh: '%s' has no UVs\n" % (obj.name, mesh.name))
+ else:
+ len_faces = len(mesh.faces)
+
+ uv_array = array.array('f', [0.0] * 8) * len_faces # seems to be the fastest way to create an array
+ mesh.uv_textures.active.data.foreach_get("uv_raw", uv_array)
+
+ objects = context.selected_editable_objects[:]
+
+ for obj_other in objects:
+ if obj_other.type == 'MESH':
+ obj_other.data.tag = False
+
+ for obj_other in objects:
+ if obj_other != obj and obj_other.type == 'MESH':
+ mesh_other = obj_other.data
+ if mesh_other != mesh:
+ if mesh_other.tag == False:
+ mesh_other.tag = True
+
+ if len(mesh_other.faces) != len_faces:
+ self.report({'WARNING'}, "Object: %s, Mesh: '%s' has %d faces, expected %d\n" % (obj_other.name, mesh_other.name, len(mesh_other.faces), len_faces))
+ else:
+ uv_other = mesh_other.uv_textures.active
+ if not uv_other:
+ uv_other = mesh_other.uv_textures.new() # should return the texture it adds
+
+ # finally do the copy
+ uv_other.data.foreach_set("uv_raw", uv_array)
+
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+
+ def execute(self, context):
+ self._main(context)
+ return {'FINISHED'}
+
+
+class MakeDupliFace(bpy.types.Operator):
+ '''Make linked objects into dupli-faces'''
+ bl_idname = "object.make_dupli_face"
+ bl_label = "Make Dupli-Face"
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return (obj and obj.type == 'MESH')
+
+ def _main(self, context):
+ from mathutils import Vector
+
+ SCALE_FAC = 0.01
+ offset = 0.5 * SCALE_FAC
+ base_tri = Vector((-offset, -offset, 0.0)), Vector((offset, -offset, 0.0)), Vector((offset, offset, 0.0)), Vector((-offset, offset, 0.0))
+
+ def matrix_to_quat(matrix):
+ # scale = matrix.median_scale
+ trans = matrix.to_translation()
+ rot = matrix.to_3x3() # also contains scale
+
+ return [(b * rot) + trans for b in base_tri]
+ scene = bpy.context.scene
+ linked = {}
+ for obj in bpy.context.selected_objects:
+ data = obj.data
+ if data:
+ linked.setdefault(data, []).append(obj)
+
+ for data, objects in linked.items():
+ face_verts = [axis for obj in objects for v in matrix_to_quat(obj.matrix_world) for axis in v]
+ faces = list(range(len(face_verts) // 3))
+
+ mesh = bpy.data.meshes.new(data.name + "_dupli")
+
+ mesh.vertices.add(len(face_verts) // 3)
+ mesh.faces.add(len(face_verts) // 12)
+
+ mesh.vertices.foreach_set("co", face_verts)
+ mesh.faces.foreach_set("vertices_raw", faces)
+ mesh.update() # generates edge data
+
+ # pick an object to use
+ obj = objects[0]
+
+ ob_new = bpy.data.objects.new(mesh.name, mesh)
+ base = scene.objects.link(ob_new)
+ base.layers[:] = obj.layers
+
+ ob_inst = bpy.data.objects.new(data.name, data)
+ base = scene.objects.link(ob_inst)
+ base.layers[:] = obj.layers
+
+ for obj in objects:
+ scene.objects.unlink(obj)
+
+ ob_new.dupli_type = 'FACES'
+ ob_inst.parent = ob_new
+ ob_new.use_dupli_faces_scale = True
+ ob_new.dupli_faces_scale = 1.0 / SCALE_FAC
+
+ def execute(self, context):
+ self._main(context)
+ return {'FINISHED'}
+
+
+class IsolateTypeRender(bpy.types.Operator):
+ '''Hide unselected render objects of same type as active by setting the hide render flag'''
+ bl_idname = "object.isolate_type_render"
+ bl_label = "Restrict Render Unselected"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ act_type = context.object.type
+
+ for obj in context.visible_objects:
+
+ if obj.select:
+ obj.hide_render = False
+ else:
+ if obj.type == act_type:
+ obj.hide_render = True
+
+ return {'FINISHED'}
+
+
+class ClearAllRestrictRender(bpy.types.Operator):
+ '''Reveal all render objects by setting the hide render flag'''
+ bl_idname = "object.hide_render_clear_all"
+ bl_label = "Clear All Restrict Render"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ for obj in context.scene.objects:
+ obj.hide_render = False
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/object_align.py b/release/scripts/startup/bl_operators/object_align.py
new file mode 100644
index 00000000000..644f30a4745
--- /dev/null
+++ b/release/scripts/startup/bl_operators/object_align.py
@@ -0,0 +1,280 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+from mathutils import Vector
+
+
+def align_objects(align_x, align_y, align_z, align_mode, relative_to):
+
+ cursor = bpy.context.scene.cursor_location
+
+ Left_Up_Front_SEL = [0.0, 0.0, 0.0]
+ Right_Down_Back_SEL = [0.0, 0.0, 0.0]
+
+ flag_first = True
+
+ objs = []
+
+ for obj in bpy.context.selected_objects:
+ matrix_world = obj.matrix_world
+ bb_world = [Vector(v[:]) * matrix_world for v in obj.bound_box]
+ objs.append((obj, bb_world))
+
+ if not objs:
+ return False
+
+ for obj, bb_world in objs:
+ Left_Up_Front = bb_world[1]
+ Right_Down_Back = bb_world[7]
+
+ # Active Center
+
+ if obj == bpy.context.active_object:
+
+ center_active_x = (Left_Up_Front[0] + Right_Down_Back[0]) / 2.0
+ center_active_y = (Left_Up_Front[1] + Right_Down_Back[1]) / 2.0
+ center_active_z = (Left_Up_Front[2] + Right_Down_Back[2]) / 2.0
+
+ size_active_x = (Right_Down_Back[0] - Left_Up_Front[0]) / 2.0
+ size_active_y = (Right_Down_Back[1] - Left_Up_Front[1]) / 2.0
+ size_active_z = (Left_Up_Front[2] - Right_Down_Back[2]) / 2.0
+
+ # Selection Center
+
+ if flag_first:
+ flag_first = False
+
+ Left_Up_Front_SEL[0] = Left_Up_Front[0]
+ Left_Up_Front_SEL[1] = Left_Up_Front[1]
+ Left_Up_Front_SEL[2] = Left_Up_Front[2]
+
+ Right_Down_Back_SEL[0] = Right_Down_Back[0]
+ Right_Down_Back_SEL[1] = Right_Down_Back[1]
+ Right_Down_Back_SEL[2] = Right_Down_Back[2]
+
+ else:
+ # X axis
+ if Left_Up_Front[0] < Left_Up_Front_SEL[0]:
+ Left_Up_Front_SEL[0] = Left_Up_Front[0]
+ # Y axis
+ if Left_Up_Front[1] < Left_Up_Front_SEL[1]:
+ Left_Up_Front_SEL[1] = Left_Up_Front[1]
+ # Z axis
+ if Left_Up_Front[2] > Left_Up_Front_SEL[2]:
+ Left_Up_Front_SEL[2] = Left_Up_Front[2]
+
+ # X axis
+ if Right_Down_Back[0] > Right_Down_Back_SEL[0]:
+ Right_Down_Back_SEL[0] = Right_Down_Back[0]
+ # Y axis
+ if Right_Down_Back[1] > Right_Down_Back_SEL[1]:
+ Right_Down_Back_SEL[1] = Right_Down_Back[1]
+ # Z axis
+ if Right_Down_Back[2] < Right_Down_Back_SEL[2]:
+ Right_Down_Back_SEL[2] = Right_Down_Back[2]
+
+ center_sel_x = (Left_Up_Front_SEL[0] + Right_Down_Back_SEL[0]) / 2.0
+ center_sel_y = (Left_Up_Front_SEL[1] + Right_Down_Back_SEL[1]) / 2.0
+ center_sel_z = (Left_Up_Front_SEL[2] + Right_Down_Back_SEL[2]) / 2.0
+
+ # Main Loop
+
+ for obj, bb_world in objs:
+
+ loc_world = obj.location
+ bb_world = [Vector(v[:]) * obj.matrix_world for v in obj.bound_box]
+
+ Left_Up_Front = bb_world[1]
+ Right_Down_Back = bb_world[7]
+
+ center_x = (Left_Up_Front[0] + Right_Down_Back[0]) / 2.0
+ center_y = (Left_Up_Front[1] + Right_Down_Back[1]) / 2.0
+ center_z = (Left_Up_Front[2] + Right_Down_Back[2]) / 2.0
+
+ positive_x = Right_Down_Back[0]
+ positive_y = Right_Down_Back[1]
+ positive_z = Left_Up_Front[2]
+
+ negative_x = Left_Up_Front[0]
+ negative_y = Left_Up_Front[1]
+ negative_z = Right_Down_Back[2]
+
+ obj_loc = obj.location
+
+ if align_x:
+
+ # Align Mode
+
+ if relative_to == 'OPT_4': # Active relative
+ if align_mode == 'OPT_1':
+ obj_x = obj_loc[0] - negative_x - size_active_x
+
+ elif align_mode == 'OPT_3':
+ obj_x = obj_loc[0] - positive_x + size_active_x
+
+ else: # Everything else relative
+ if align_mode == 'OPT_1':
+ obj_x = obj_loc[0] - negative_x
+
+ elif align_mode == 'OPT_3':
+ obj_x = obj_loc[0] - positive_x
+
+ if align_mode == 'OPT_2': # All relative
+ obj_x = obj_loc[0] - center_x
+
+ # Relative To
+
+ if relative_to == 'OPT_1':
+ loc_x = obj_x
+
+ elif relative_to == 'OPT_2':
+ loc_x = obj_x + cursor[0]
+
+ elif relative_to == 'OPT_3':
+ loc_x = obj_x + center_sel_x
+
+ elif relative_to == 'OPT_4':
+ loc_x = obj_x + center_active_x
+
+ obj.location[0] = loc_x
+
+ if align_y:
+ # Align Mode
+
+ if relative_to == 'OPT_4': # Active relative
+ if align_mode == 'OPT_1':
+ obj_y = obj_loc[1] - negative_y - size_active_y
+
+ elif align_mode == 'OPT_3':
+ obj_y = obj_loc[1] - positive_y + size_active_y
+
+ else: # Everything else relative
+ if align_mode == 'OPT_1':
+ obj_y = obj_loc[1] - negative_y
+
+ elif align_mode == 'OPT_3':
+ obj_y = obj_loc[1] - positive_y
+
+ if align_mode == 'OPT_2': # All relative
+ obj_y = obj_loc[1] - center_y
+
+ # Relative To
+
+ if relative_to == 'OPT_1':
+ loc_y = obj_y
+
+ elif relative_to == 'OPT_2':
+ loc_y = obj_y + cursor[1]
+
+ elif relative_to == 'OPT_3':
+ loc_y = obj_y + center_sel_y
+
+ elif relative_to == 'OPT_4':
+ loc_y = obj_y + center_active_y
+
+ obj.location[1] = loc_y
+
+ if align_z:
+ # Align Mode
+ if relative_to == 'OPT_4': # Active relative
+ if align_mode == 'OPT_1':
+ obj_z = obj_loc[2] - negative_z - size_active_z
+
+ elif align_mode == 'OPT_3':
+ obj_z = obj_loc[2] - positive_z + size_active_z
+
+ else: # Everything else relative
+ if align_mode == 'OPT_1':
+ obj_z = obj_loc[2] - negative_z
+
+ elif align_mode == 'OPT_3':
+ obj_z = obj_loc[2] - positive_z
+
+ if align_mode == 'OPT_2': # All relative
+ obj_z = obj_loc[2] - center_z
+
+ # Relative To
+
+ if relative_to == 'OPT_1':
+ loc_z = obj_z
+
+ elif relative_to == 'OPT_2':
+ loc_z = obj_z + cursor[2]
+
+ elif relative_to == 'OPT_3':
+ loc_z = obj_z + center_sel_z
+
+ elif relative_to == 'OPT_4':
+ loc_z = obj_z + center_active_z
+
+ obj.location[2] = loc_z
+
+ return True
+
+
+from bpy.props import EnumProperty
+
+
+class AlignObjects(bpy.types.Operator):
+ '''Align Objects'''
+ bl_idname = "object.align"
+ bl_label = "Align Objects"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ align_mode = EnumProperty(items=(
+ ('OPT_1', "Negative Sides", ""),
+ ('OPT_2', "Centers", ""),
+ ('OPT_3', "Positive Sides", "")),
+ name="Align Mode:",
+ description="",
+ default='OPT_2')
+
+ relative_to = EnumProperty(items=(
+ ('OPT_1', "Scene Origin", ""),
+ ('OPT_2', "3D Cursor", ""),
+ ('OPT_3', "Selection", ""),
+ ('OPT_4', "Active", "")),
+ name="Relative To:",
+ description="",
+ default='OPT_4')
+
+ align_axis = EnumProperty(items=(
+ ('X', "X", ""),
+ ('Y', "Y", ""),
+ ('Z', "Z", ""),
+ ),
+ name="Align",
+ description="Align to axis",
+ options={'ENUM_FLAG'})
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'OBJECT'
+
+ def execute(self, context):
+ align_axis = self.align_axis
+ ret = align_objects('X' in align_axis, 'Y' in align_axis, 'Z' in align_axis, self.align_mode, self.relative_to)
+
+ if not ret:
+ self.report({'WARNING'}, "No objects with bound-box selected")
+ return {'CANCELLED'}
+ else:
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/object_randomize_transform.py b/release/scripts/startup/bl_operators/object_randomize_transform.py
new file mode 100644
index 00000000000..d72c600ef78
--- /dev/null
+++ b/release/scripts/startup/bl_operators/object_randomize_transform.py
@@ -0,0 +1,147 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+
+
+def randomize_selected(seed, delta, loc, rot, scale, scale_even):
+
+ import random
+ from random import uniform
+ from mathutils import Vector
+
+ random.seed(seed)
+
+ def rand_vec(vec_range):
+ return Vector(uniform(-val, val) for val in vec_range)
+
+ for obj in bpy.context.selected_objects:
+
+ if loc:
+ if delta:
+ obj.delta_location += rand_vec(loc)
+ else:
+ obj.location += rand_vec(loc)
+ else: # otherwise the values change under us
+ uniform(0.0, 0.0), uniform(0.0, 0.0), uniform(0.0, 0.0)
+
+ if rot: # TODO, non euler's
+ vec = rand_vec(rot)
+ if delta:
+ obj.delta_rotation_euler[0] += vec[0]
+ obj.delta_rotation_euler[1] += vec[1]
+ obj.delta_rotation_euler[2] += vec[2]
+ else:
+ obj.rotation_euler[0] += vec[0]
+ obj.rotation_euler[1] += vec[1]
+ obj.rotation_euler[2] += vec[2]
+ else:
+ uniform(0.0, 0.0), uniform(0.0, 0.0), uniform(0.0, 0.0)
+
+ if scale:
+ if delta:
+ org_sca_x, org_sca_y, org_sca_z = obj.delta_scale
+ else:
+ org_sca_x, org_sca_y, org_sca_z = obj.scale
+
+ if scale_even:
+ sca_x = sca_y = sca_z = uniform(scale[0], - scale[0])
+ uniform(0.0, 0.0), uniform(0.0, 0.0)
+ else:
+ sca_x, sca_y, sca_z = rand_vec(scale)
+
+ if scale_even:
+ aX = -(sca_x * org_sca_x) + org_sca_x
+ aY = -(sca_x * org_sca_y) + org_sca_y
+ aZ = -(sca_x * org_sca_z) + org_sca_z
+ else:
+ aX = sca_x + org_sca_x
+ aY = sca_y + org_sca_y
+ aZ = sca_z + org_sca_z
+
+ if delta:
+ obj.delta_scale = aX, aY, aZ
+ else:
+ obj.scale = aX, aY, aZ
+ else:
+ uniform(0.0, 0.0), uniform(0.0, 0.0), uniform(0.0, 0.0)
+
+
+from bpy.props import IntProperty, BoolProperty, FloatProperty, FloatVectorProperty
+
+
+class RandomizeLocRotSize(bpy.types.Operator):
+ '''Randomize objects loc/rot/scale'''
+ bl_idname = "object.randomize_transform"
+ bl_label = "Randomize Transform"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ random_seed = IntProperty(name="Random Seed",
+ description="Seed value for the random generator",
+ default=0, min=0, max=1000)
+
+ use_delta = BoolProperty(name="Transform Delta",
+ description="Randomize delta transform values instead of regular transform", default=False)
+
+ use_loc = BoolProperty(name="Randomize Location",
+ description="Randomize the location values", default=True)
+
+ loc = FloatVectorProperty(name="Location",
+ description="Maximun distance the objects can spread over each axis",
+ default=(0.0, 0.0, 0.0), min=-100.0, max=100.0, subtype='TRANSLATION')
+
+ use_rot = BoolProperty(name="Randomize Rotation",
+ description="Randomize the rotation values", default=True)
+
+ rot = FloatVectorProperty(name="Rotation",
+ description="Maximun rotation over each axis",
+ default=(0.0, 0.0, 0.0), min=-180.0, max=180.0, subtype='TRANSLATION')
+
+ use_scale = BoolProperty(name="Randomize Scale",
+ description="Randomize the scale values", default=True)
+
+ scale_even = BoolProperty(name="Scale Even",
+ description="Use the same scale value for all axis", default=False)
+
+ '''scale_min = FloatProperty(name="Minimun Scale Factor",
+ description="Lowest scale percentage possible",
+ default=0.15, min=-1.0, max=1.0, precision=3)'''
+
+ scale = FloatVectorProperty(name="Scale",
+ description="Maximum scale randomization over each axis",
+ default=(0.0, 0.0, 0.0), min=-100.0, max=100.0, subtype='TRANSLATION')
+
+ def execute(self, context):
+ from math import radians
+
+ seed = self.random_seed
+
+ delta = self.use_delta
+
+ loc = None if not self.use_loc else self.loc
+ rot = None if not self.use_rot else self.rot * radians(1.0)
+ scale = None if not self.use_scale else self.scale
+
+ scale_even = self.scale_even
+ #scale_min = self.scale_min
+
+ randomize_selected(seed, delta, loc, rot, scale, scale_even)
+
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/presets.py b/release/scripts/startup/bl_operators/presets.py
new file mode 100644
index 00000000000..e6f71ef9573
--- /dev/null
+++ b/release/scripts/startup/bl_operators/presets.py
@@ -0,0 +1,353 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+import os
+
+
+class AddPresetBase():
+ '''Base preset class, only for subclassing
+ subclasses must define
+ - preset_values
+ - preset_subdir '''
+ # bl_idname = "script.preset_base_add"
+ # bl_label = "Add a Python Preset"
+ bl_options = {'REGISTER'} # only because invoke_props_popup requires.
+
+ name = bpy.props.StringProperty(name="Name", description="Name of the preset, used to make the path name", maxlen=64, default="")
+ remove_active = bpy.props.BoolProperty(default=False, options={'HIDDEN'})
+
+ @staticmethod
+ def as_filename(name): # could reuse for other presets
+ for char in " !@#$%^&*(){}:\";'[]<>,.\\/?":
+ name = name.replace(char, '_')
+ return name.lower().strip()
+
+ def execute(self, context):
+ import os
+
+ if hasattr(self, "pre_cb"):
+ self.pre_cb(context)
+
+ preset_menu_class = getattr(bpy.types, self.preset_menu)
+
+ if not self.remove_active:
+
+ if not self.name:
+ return {'FINISHED'}
+
+ filename = self.as_filename(self.name)
+
+ target_path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", self.preset_subdir), create=True)
+
+ if not target_path:
+ self.report({'WARNING'}, "Failed to create presets path")
+ return {'CANCELLED'}
+
+ filepath = os.path.join(target_path, filename) + ".py"
+
+ if hasattr(self, "add"):
+ self.add(context, filepath)
+ else:
+ file_preset = open(filepath, 'w')
+ file_preset.write("import bpy\n")
+
+ if hasattr(self, "preset_defines"):
+ for rna_path in self.preset_defines:
+ exec(rna_path)
+ file_preset.write("%s\n" % rna_path)
+ file_preset.write("\n")
+
+ for rna_path in self.preset_values:
+ value = eval(rna_path)
+ # convert thin wrapped sequences to simple lists to repr()
+ try:
+ value = value[:]
+ except:
+ pass
+
+ file_preset.write("%s = %r\n" % (rna_path, value))
+
+ file_preset.close()
+
+ preset_menu_class.bl_label = bpy.path.display_name(filename)
+
+ else:
+ preset_active = preset_menu_class.bl_label
+
+ # fairly sloppy but convenient.
+ filepath = bpy.utils.preset_find(preset_active, self.preset_subdir)
+
+ if not filepath:
+ filepath = bpy.utils.preset_find(preset_active, self.preset_subdir, display_name=True)
+
+ if not filepath:
+ return {'CANCELLED'}
+
+ if hasattr(self, "remove"):
+ self.remove(context, filepath)
+ else:
+ try:
+ os.remove(filepath)
+ except:
+ import traceback
+ traceback.print_exc()
+
+ # XXX, stupid!
+ preset_menu_class.bl_label = "Presets"
+
+ if hasattr(self, "post_cb"):
+ self.post_cb(context)
+
+ return {'FINISHED'}
+
+ def check(self, context):
+ self.name = self.as_filename(self.name)
+
+ def invoke(self, context, event):
+ if not self.remove_active:
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self)
+ else:
+ return self.execute(context)
+
+
+class ExecutePreset(bpy.types.Operator):
+ ''' Executes a preset '''
+ bl_idname = "script.execute_preset"
+ bl_label = "Execute a Python Preset"
+
+ filepath = bpy.props.StringProperty(name="Path", description="Path of the Python file to execute", maxlen=512, default="")
+ menu_idname = bpy.props.StringProperty(name="Menu ID Name", description="ID name of the menu this was called from", default="")
+
+ def execute(self, context):
+ from os.path import basename
+ filepath = self.filepath
+
+ # change the menu title to the most recently chosen option
+ preset_class = getattr(bpy.types, self.menu_idname)
+ preset_class.bl_label = bpy.path.display_name(basename(filepath))
+
+ # execute the preset using script.python_file_run
+ bpy.ops.script.python_file_run(filepath=filepath)
+ return {'FINISHED'}
+
+
+class AddPresetRender(AddPresetBase, bpy.types.Operator):
+ '''Add a Render Preset'''
+ bl_idname = "render.preset_add"
+ bl_label = "Add Render Preset"
+ preset_menu = "RENDER_MT_presets"
+
+ preset_defines = [
+ "scene = bpy.context.scene"
+ ]
+
+ preset_values = [
+ "scene.render.field_order",
+ "scene.render.fps",
+ "scene.render.fps_base",
+ "scene.render.pixel_aspect_x",
+ "scene.render.pixel_aspect_y",
+ "scene.render.resolution_percentage",
+ "scene.render.resolution_x",
+ "scene.render.resolution_y",
+ "scene.render.use_fields",
+ "scene.render.use_fields_still",
+ ]
+
+ preset_subdir = "render"
+
+
+class AddPresetSSS(AddPresetBase, bpy.types.Operator):
+ '''Add a Subsurface Scattering Preset'''
+ bl_idname = "material.sss_preset_add"
+ bl_label = "Add SSS Preset"
+ preset_menu = "MATERIAL_MT_sss_presets"
+
+ preset_defines = [
+ "material = (bpy.context.material.active_node_material if bpy.context.material.active_node_material else bpy.context.material)"
+ ]
+
+ preset_values = [
+ "material.subsurface_scattering.back",
+ "material.subsurface_scattering.color",
+ "material.subsurface_scattering.color_factor",
+ "material.subsurface_scattering.error_threshold",
+ "material.subsurface_scattering.front",
+ "material.subsurface_scattering.ior",
+ "material.subsurface_scattering.radius",
+ "material.subsurface_scattering.scale",
+ "material.subsurface_scattering.texture_factor",
+ ]
+
+ preset_subdir = "sss"
+
+
+class AddPresetCloth(AddPresetBase, bpy.types.Operator):
+ '''Add a Cloth Preset'''
+ bl_idname = "cloth.preset_add"
+ bl_label = "Add Cloth Preset"
+ preset_menu = "CLOTH_MT_presets"
+
+ preset_defines = [
+ "cloth = bpy.context.cloth"
+ ]
+
+ preset_values = [
+ "cloth.settings.air_damping",
+ "cloth.settings.bending_stiffness",
+ "cloth.settings.mass",
+ "cloth.settings.quality",
+ "cloth.settings.spring_damping",
+ "cloth.settings.structural_stiffness",
+ ]
+
+ preset_subdir = "cloth"
+
+
+class AddPresetSunSky(AddPresetBase, bpy.types.Operator):
+ '''Add a Sky & Atmosphere Preset'''
+ bl_idname = "lamp.sunsky_preset_add"
+ bl_label = "Add Sunsky Preset"
+ preset_menu = "LAMP_MT_sunsky_presets"
+
+ preset_defines = [
+ "sky = bpy.context.object.data.sky"
+ ]
+
+ preset_values = [
+ "sky.atmosphere_extinction",
+ "sky.atmosphere_inscattering",
+ "sky.atmosphere_turbidity",
+ "sky.backscattered_light",
+ "sky.horizon_brightness",
+ "sky.spread",
+ "sky.sun_brightness",
+ "sky.sun_intensity",
+ "sky.sun_size",
+ "sky.use_sky_blend",
+ "sky.use_sky_blend_type",
+ "sky.use_sky_color_space",
+ "sky.use_sky_exposure",
+ ]
+
+ preset_subdir = "sunsky"
+
+
+class AddPresetInteraction(AddPresetBase, bpy.types.Operator):
+ '''Add an Application Interaction Preset'''
+ bl_idname = "wm.interaction_preset_add"
+ bl_label = "Add Interaction Preset"
+ preset_menu = "USERPREF_MT_interaction_presets"
+
+ preset_defines = [
+ "user_preferences = bpy.context.user_preferences"
+ ]
+
+ preset_values = [
+ "user_preferences.edit.use_drag_immediately",
+ "user_preferences.edit.use_insertkey_xyz_to_rgb",
+ "user_preferences.inputs.invert_mouse_wheel_zoom",
+ "user_preferences.inputs.select_mouse",
+ "user_preferences.inputs.use_emulate_numpad",
+ "user_preferences.inputs.use_mouse_continuous",
+ "user_preferences.inputs.use_mouse_emulate_3_button",
+ "user_preferences.inputs.view_rotate_method",
+ "user_preferences.inputs.view_zoom_axis",
+ "user_preferences.inputs.view_zoom_method",
+ ]
+
+ preset_subdir = "interaction"
+
+
+class AddPresetKeyconfig(AddPresetBase, bpy.types.Operator):
+ '''Add a Keyconfig Preset'''
+ bl_idname = "wm.keyconfig_preset_add"
+ bl_label = "Add Keyconfig Preset"
+ preset_menu = "USERPREF_MT_keyconfigs"
+ preset_subdir = "keyconfig"
+
+ def add(self, context, filepath):
+ bpy.ops.wm.keyconfig_export(filepath=filepath)
+ bpy.utils.keyconfig_set(filepath)
+
+ def pre_cb(self, context):
+ keyconfigs = bpy.context.window_manager.keyconfigs
+ if self.remove_active:
+ preset_menu_class = getattr(bpy.types, self.preset_menu)
+ preset_menu_class.bl_label = keyconfigs.active.name
+
+ def post_cb(self, context):
+ keyconfigs = bpy.context.window_manager.keyconfigs
+ if self.remove_active:
+ keyconfigs.remove(keyconfigs.active)
+
+
+class AddPresetOperator(AddPresetBase, bpy.types.Operator):
+ '''Add an Application Interaction Preset'''
+ bl_idname = "wm.operator_preset_add"
+ bl_label = "Operator Preset"
+ preset_menu = "WM_MT_operator_presets"
+
+ operator = bpy.props.StringProperty(name="Operator", maxlen=64, options={'HIDDEN'})
+
+ # XXX, not ideal
+ preset_defines = [
+ "op = bpy.context.space_data.operator",
+ ]
+
+ @property
+ def preset_subdir(self):
+ return __class__.operator_path(self.operator)
+
+ @property
+ def preset_values(self):
+ properties_blacklist = bpy.types.Operator.bl_rna.properties.keys()
+
+ prefix, suffix = self.operator.split("_OT_", 1)
+ operator_rna = getattr(getattr(bpy.ops, prefix.lower()), suffix).get_rna().bl_rna
+
+ ret = []
+ for prop_id, prop in operator_rna.properties.items():
+ if (not prop.is_hidden) and prop_id not in properties_blacklist:
+ ret.append("op.%s" % prop_id)
+
+ return ret
+
+ @staticmethod
+ def operator_path(operator):
+ import os
+ prefix, suffix = operator.split("_OT_", 1)
+ return os.path.join("operator", "%s.%s" % (prefix.lower(), suffix))
+
+
+class WM_MT_operator_presets(bpy.types.Menu):
+ bl_label = "Operator Presets"
+
+ def draw(self, context):
+ self.operator = context.space_data.operator.bl_idname
+ bpy.types.Menu.draw_preset(self, context)
+
+ @property
+ def preset_subdir(self):
+ return AddPresetOperator.operator_path(self.operator)
+
+ preset_operator = "script.execute_preset"
diff --git a/release/scripts/startup/bl_operators/screen_play_rendered_anim.py b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py
new file mode 100644
index 00000000000..64af25e7b0f
--- /dev/null
+++ b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py
@@ -0,0 +1,142 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# Script copyright (C) Campbell J Barton
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+# <pep8 compliant>
+
+# History
+#
+# Originally written by Matt Ebb
+
+import bpy
+import os
+
+
+def guess_player_path(preset):
+ import sys
+
+ if preset == 'BLENDER24':
+ player_path = "blender"
+
+ if sys.platform == "darwin":
+ test_path = "/Applications/blender 2.49.app/Contents/MacOS/blender"
+ elif sys.platform[:3] == "win":
+ test_path = "/Program Files/Blender Foundation/Blender/blender.exe"
+
+ if os.path.exists(test_path):
+ player_path = test_path
+
+ elif preset == 'DJV':
+ player_path = "djv_view"
+
+ if sys.platform == "darwin":
+ # TODO, crummy supporting only 1 version, could find the newest installed version
+ test_path = '/Applications/djv-0.8.2.app/Contents/Resources/bin/djv_view'
+ if os.path.exists(test_path):
+ player_path = test_path
+
+ elif preset == 'FRAMECYCLER':
+ player_path = "framecycler"
+
+ elif preset == 'RV':
+ player_path = "rv"
+
+ elif preset == 'MPLAYER':
+ player_path = "mplayer"
+
+ return player_path
+
+
+class PlayRenderedAnim(bpy.types.Operator):
+ '''Plays back rendered frames/movies using an external player.'''
+ bl_idname = "render.play_rendered_anim"
+ bl_label = "Play Rendered Animation"
+ bl_options = {'REGISTER'}
+
+ def execute(self, context):
+ import subprocess
+
+ scene = context.scene
+ rd = scene.render
+ prefs = context.user_preferences
+
+ preset = prefs.filepaths.animation_player_preset
+ player_path = prefs.filepaths.animation_player
+ file_path = bpy.path.abspath(rd.filepath)
+ is_movie = rd.is_movie_format
+
+ # try and guess a command line if it doesn't exist
+ if player_path == '':
+ player_path = guess_player_path(preset)
+
+ if is_movie == False and preset in ('FRAMECYCLER', 'RV', 'MPLAYER'):
+ # replace the number with '#'
+ file_a = rd.frame_path(frame=0)
+
+ # TODO, make an api call for this
+ frame_tmp = 9
+ file_b = rd.frame_path(frame=frame_tmp)
+
+ while len(file_a) == len(file_b):
+ frame_tmp = (frame_tmp * 10) + 9
+ print(frame_tmp)
+ file_b = rd.frame_path(frame=frame_tmp)
+ file_b = rd.frame_path(frame=int(frame_tmp / 10))
+
+ file = "".join((c if file_b[i] == c else "#") for i, c in enumerate(file_a))
+ else:
+ # works for movies and images
+ file = rd.frame_path(frame=scene.frame_start)
+
+ file = bpy.path.abspath(file) # expand '//'
+
+ cmd = [player_path]
+ # extra options, fps controls etc.
+ if preset == 'BLENDER24':
+ opts = ["-a", "-f", str(rd.fps), str(rd.fps_base), file]
+ cmd.extend(opts)
+ elif preset == 'DJV':
+ opts = [file, "-playback_speed", str(rd.fps)]
+ cmd.extend(opts)
+ elif preset == 'FRAMECYCLER':
+ opts = [file, "%d-%d" % (scene.frame_start, scene.frame_end)]
+ cmd.extend(opts)
+ elif preset == 'RV':
+ opts = ["-fps", str(rd.fps), "-play", "[ %s ]" % file]
+ cmd.extend(opts)
+ elif preset == 'MPLAYER':
+ opts = []
+ if is_movie:
+ opts.append(file)
+ else:
+ opts.append("mf://%s" % file.replace("#", "?"))
+ opts += ["-mf", "fps=%.4f" % (rd.fps / rd.fps_base)]
+ opts += ["-loop", "0", "-really-quiet", "-fs"]
+ cmd.extend(opts)
+ else: # 'CUSTOM'
+ cmd.append(file)
+
+ # launch it
+ try:
+ process = subprocess.Popen(cmd)
+ except:
+ pass
+ #raise OSError("Couldn't find an external animation player.")
+
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/sequencer.py b/release/scripts/startup/bl_operators/sequencer.py
new file mode 100644
index 00000000000..16b72406c49
--- /dev/null
+++ b/release/scripts/startup/bl_operators/sequencer.py
@@ -0,0 +1,134 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+
+from bpy.props import IntProperty
+
+
+class SequencerCrossfadeSounds(bpy.types.Operator):
+ '''Do crossfading volume animation of two selected sound strips.'''
+
+ bl_idname = "sequencer.crossfade_sounds"
+ bl_label = "Crossfade sounds"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
+ return context.scene.sequence_editor.active_strip.type == 'SOUND'
+ else:
+ return False
+
+ def execute(self, context):
+ seq1 = None
+ seq2 = None
+ for s in context.scene.sequence_editor.sequences:
+ if s.select and s.type == 'SOUND':
+ if seq1 is None:
+ seq1 = s
+ elif seq2 is None:
+ seq2 = s
+ else:
+ seq2 = None
+ break
+ if seq2 is None:
+ self.report({'ERROR'}, "Select 2 sound strips.")
+ return {'CANCELLED'}
+ if seq1.frame_final_start > seq2.frame_final_start:
+ s = seq1
+ seq1 = seq2
+ seq2 = s
+ if seq1.frame_final_end > seq2.frame_final_start:
+ tempcfra = context.scene.frame_current
+ context.scene.frame_current = seq2.frame_final_start
+ seq1.keyframe_insert('volume')
+ context.scene.frame_current = seq1.frame_final_end
+ seq1.volume = 0
+ seq1.keyframe_insert('volume')
+ seq2.keyframe_insert('volume')
+ context.scene.frame_current = seq2.frame_final_start
+ seq2.volume = 0
+ seq2.keyframe_insert('volume')
+ context.scene.frame_current = tempcfra
+ return {'FINISHED'}
+ else:
+ self.report({'ERROR'}, "The selected strips don't overlap.")
+ return {'CANCELLED'}
+
+
+class SequencerCutMulticam(bpy.types.Operator):
+ '''Cut multicam strip and select camera.'''
+
+ bl_idname = "sequencer.cut_multicam"
+ bl_label = "Cut multicam"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ camera = IntProperty(name="Camera",
+ default=1, min=1, max=32, soft_min=1, soft_max=32)
+
+ @classmethod
+ def poll(cls, context):
+ if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
+ return context.scene.sequence_editor.active_strip.type == 'MULTICAM'
+ else:
+ return False
+
+ def execute(self, context):
+ camera = self.camera
+
+ s = context.scene.sequence_editor.active_strip
+
+ if s.multicam_source == camera or camera >= s.channel:
+ return {'FINISHED'}
+
+ if not s.select:
+ s.select = True
+
+ cfra = context.scene.frame_current
+ bpy.ops.sequencer.cut(frame=cfra, type='SOFT', side='RIGHT')
+ for s in context.scene.sequence_editor.sequences_all:
+ if s.select and s.type == 'MULTICAM' and s.frame_final_start <= cfra and cfra < s.frame_final_end:
+ context.scene.sequence_editor.active_strip = s
+
+ context.scene.sequence_editor.active_strip.multicam_source = camera
+ return {'FINISHED'}
+
+
+class SequencerDeinterlaceSelectedMovies(bpy.types.Operator):
+ '''Deinterlace all selected movie sources.'''
+
+ bl_idname = "sequencer.deinterlace_selected_movies"
+ bl_label = "Deinterlace Movies"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ if context.scene and context.scene.sequence_editor:
+ return True
+ else:
+ return False
+
+ def execute(self, context):
+ for s in context.scene.sequence_editor.sequences_all:
+ if s.select and s.type == 'MOVIE':
+ s.use_deinterlace = True
+
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/uvcalc_follow_active.py b/release/scripts/startup/bl_operators/uvcalc_follow_active.py
new file mode 100644
index 00000000000..ad5ec15ff80
--- /dev/null
+++ b/release/scripts/startup/bl_operators/uvcalc_follow_active.py
@@ -0,0 +1,250 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+#for full docs see...
+# http://mediawiki.blender.org/index.php/Scripts/Manual/UV_Calculate/Follow_active_quads
+
+import bpy
+
+
+def extend(obj, operator, EXTEND_MODE):
+ me = obj.data
+ me_verts = me.vertices
+ # script will fail without UVs
+ if not me.uv_textures:
+ me.uv_textures.new()
+
+ # Toggle Edit mode
+ is_editmode = (obj.mode == 'EDIT')
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+ #t = sys.time()
+ edge_average_lengths = {}
+
+ OTHER_INDEX = 2, 3, 0, 1
+ FAST_INDICIES = 0, 2, 1, 3 # order is faster
+
+ def extend_uvs(face_source, face_target, edge_key):
+ '''
+ Takes 2 faces,
+ Projects its extends its UV coords onto the face next to it.
+ Both faces must share an edge
+ '''
+
+ def face_edge_vs(vi):
+ # assume a quad
+ return [(vi[0], vi[1]), (vi[1], vi[2]), (vi[2], vi[3]), (vi[3], vi[0])]
+
+ vidx_source = face_source.vertices
+ vidx_target = face_target.vertices
+
+ faceUVsource = me.uv_textures.active.data[face_source.index]
+ uvs_source = [faceUVsource.uv1, faceUVsource.uv2, faceUVsource.uv3, faceUVsource.uv4]
+
+ faceUVtarget = me.uv_textures.active.data[face_target.index]
+ uvs_target = [faceUVtarget.uv1, faceUVtarget.uv2, faceUVtarget.uv3, faceUVtarget.uv4]
+
+ # vertex index is the key, uv is the value
+
+ uvs_vhash_source = {vindex: uvs_source[i] for i, vindex in enumerate(vidx_source)}
+
+ uvs_vhash_target = {vindex: uvs_target[i] for i, vindex in enumerate(vidx_target)}
+
+ edge_idxs_source = face_edge_vs(vidx_source)
+ edge_idxs_target = face_edge_vs(vidx_target)
+
+ source_matching_edge = -1
+ target_matching_edge = -1
+
+ edge_key_swap = edge_key[1], edge_key[0]
+
+ try:
+ source_matching_edge = edge_idxs_source.index(edge_key)
+ except:
+ source_matching_edge = edge_idxs_source.index(edge_key_swap)
+ try:
+ target_matching_edge = edge_idxs_target.index(edge_key)
+ except:
+ target_matching_edge = edge_idxs_target.index(edge_key_swap)
+
+ edgepair_inner_source = edge_idxs_source[source_matching_edge]
+ edgepair_inner_target = edge_idxs_target[target_matching_edge]
+ edgepair_outer_source = edge_idxs_source[OTHER_INDEX[source_matching_edge]]
+ edgepair_outer_target = edge_idxs_target[OTHER_INDEX[target_matching_edge]]
+
+ if edge_idxs_source[source_matching_edge] == edge_idxs_target[target_matching_edge]:
+ iA = 0 # Flipped, most common
+ iB = 1
+ else: # The normals of these faces must be different
+ iA = 1
+ iB = 0
+
+ # Set the target UV's touching source face, no tricky calc needed,
+ uvs_vhash_target[edgepair_inner_target[0]][:] = uvs_vhash_source[edgepair_inner_source[iA]]
+ uvs_vhash_target[edgepair_inner_target[1]][:] = uvs_vhash_source[edgepair_inner_source[iB]]
+
+ # Set the 2 UV's on the target face that are not touching
+ # for this we need to do basic expaning on the source faces UV's
+ if EXTEND_MODE == 'LENGTH':
+
+ try: # divide by zero is possible
+ '''
+ measure the length of each face from the middle of each edge to the opposite
+ allong the axis we are copying, use this
+ '''
+ i1a = edgepair_outer_target[iB]
+ i2a = edgepair_inner_target[iA]
+ if i1a > i2a:
+ i1a, i2a = i2a, i1a
+
+ i1b = edgepair_outer_source[iB]
+ i2b = edgepair_inner_source[iA]
+ if i1b > i2b:
+ i1b, i2b = i2b, i1b
+ # print edge_average_lengths
+ factor = edge_average_lengths[i1a, i2a][0] / edge_average_lengths[i1b, i2b][0]
+ except:
+ # Div By Zero?
+ factor = 1.0
+
+ uvs_vhash_target[edgepair_outer_target[iB]][:] = uvs_vhash_source[edgepair_inner_source[0]] + factor * (uvs_vhash_source[edgepair_inner_source[0]] - uvs_vhash_source[edgepair_outer_source[1]])
+ uvs_vhash_target[edgepair_outer_target[iA]][:] = uvs_vhash_source[edgepair_inner_source[1]] + factor * (uvs_vhash_source[edgepair_inner_source[1]] - uvs_vhash_source[edgepair_outer_source[0]])
+
+ else:
+ # same as above but with no factors
+ uvs_vhash_target[edgepair_outer_target[iB]][:] = uvs_vhash_source[edgepair_inner_source[0]] + (uvs_vhash_source[edgepair_inner_source[0]] - uvs_vhash_source[edgepair_outer_source[1]])
+ uvs_vhash_target[edgepair_outer_target[iA]][:] = uvs_vhash_source[edgepair_inner_source[1]] + (uvs_vhash_source[edgepair_inner_source[1]] - uvs_vhash_source[edgepair_outer_source[0]])
+
+ if not me.uv_textures:
+ me.uv_textures.new()
+
+ face_act = me.faces.active
+ if face_act == -1:
+ operator.report({'ERROR'}, "No active face.")
+ return
+
+ face_sel = [f for f in me.faces if len(f.vertices) == 4 and f.select]
+
+ face_act_local_index = -1
+ for i, f in enumerate(face_sel):
+ if f.index == face_act:
+ face_act_local_index = i
+ break
+
+ if face_act_local_index == -1:
+ operator.report({'ERROR'}, "Active face not selected.")
+ return
+
+ # Modes
+ # 0 unsearched
+ # 1:mapped, use search from this face. - removed!!
+ # 2:all siblings have been searched. dont search again.
+ face_modes = [0] * len(face_sel)
+ face_modes[face_act_local_index] = 1 # extend UV's from this face.
+
+ # Edge connectivty
+ edge_faces = {}
+ for i, f in enumerate(face_sel):
+ for edkey in f.edge_keys:
+ try:
+ edge_faces[edkey].append(i)
+ except:
+ edge_faces[edkey] = [i]
+
+ if EXTEND_MODE == 'LENGTH':
+ edge_loops = me.edge_loops_from_faces(face_sel, [ed.key for ed in me.edges if ed.use_seam])
+ me_verts = me.vertices
+ for loop in edge_loops:
+ looplen = [0.0]
+ for ed in loop:
+ edge_average_lengths[ed] = looplen
+ looplen[0] += (me_verts[ed[0]].co - me_verts[ed[1]].co).length
+ looplen[0] = looplen[0] / len(loop)
+
+ # remove seams, so we dont map accross seams.
+ for ed in me.edges:
+ if ed.use_seam:
+ # remove the edge pair if we can
+ try:
+ del edge_faces[ed.key]
+ except:
+ pass
+ # Done finding seams
+
+ # face connectivity - faces around each face
+ # only store a list of indices for each face.
+ face_faces = [[] for i in range(len(face_sel))]
+
+ for edge_key, faces in edge_faces.items():
+ if len(faces) == 2: # Only do edges with 2 face users for now
+ face_faces[faces[0]].append((faces[1], edge_key))
+ face_faces[faces[1]].append((faces[0], edge_key))
+
+ # Now we know what face is connected to what other face, map them by connectivity
+ ok = True
+ while ok:
+ ok = False
+ for i in range(len(face_sel)):
+ if face_modes[i] == 1: # searchable
+ for f_sibling, edge_key in face_faces[i]:
+ if face_modes[f_sibling] == 0:
+ face_modes[f_sibling] = 1 # mapped and search from.
+ extend_uvs(face_sel[i], face_sel[f_sibling], edge_key)
+ face_modes[i] = 1 # we can map from this one now.
+ ok = True # keep searching
+
+ face_modes[i] = 2 # dont search again
+
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='EDIT')
+ else:
+ me.update_tag()
+
+
+def main(context, operator):
+ obj = context.active_object
+
+ extend(obj, operator, operator.properties.mode)
+
+
+class FollowActiveQuads(bpy.types.Operator):
+ '''Follow UVs from active quads along continuous face loops'''
+ bl_idname = "uv.follow_active_quads"
+ bl_label = "Follow Active Quads"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ mode = bpy.props.EnumProperty(items=(("EVEN", "Even", "Space all UVs evently"), ("LENGTH", "Length", "Average space UVs edge length of each loop")),
+ name="Edge Length Mode",
+ description="Method to space UV edge loops",
+ default="LENGTH")
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return (obj is not None and obj.type == 'MESH')
+
+ def execute(self, context):
+ main(context, self)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self)
diff --git a/release/scripts/startup/bl_operators/uvcalc_lightmap.py b/release/scripts/startup/bl_operators/uvcalc_lightmap.py
new file mode 100644
index 00000000000..fedc8b15161
--- /dev/null
+++ b/release/scripts/startup/bl_operators/uvcalc_lightmap.py
@@ -0,0 +1,582 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+import mathutils
+
+from math import sqrt, pi
+
+
+class prettyface(object):
+ __slots__ = "uv", "width", "height", "children", "xoff", "yoff", "has_parent", "rot"
+
+ def __init__(self, data):
+ self.has_parent = False
+ self.rot = False # only used for triables
+ self.xoff = 0
+ self.yoff = 0
+
+ if type(data) == list: # list of data
+ self.uv = None
+
+ # join the data
+ if len(data) == 2:
+ # 2 vertical blocks
+ data[1].xoff = data[0].width
+ self.width = data[0].width * 2
+ self.height = data[0].height
+
+ elif len(data) == 4:
+ # 4 blocks all the same size
+ d = data[0].width # dimension x/y are the same
+
+ data[1].xoff += d
+ data[2].yoff += d
+
+ data[3].xoff += d
+ data[3].yoff += d
+
+ self.width = self.height = d * 2
+
+ #else:
+ # print(len(data), data)
+ # raise "Error"
+
+ for pf in data:
+ pf.has_parent = True
+
+ self.children = data
+
+ elif type(data) == tuple:
+ # 2 blender faces
+ # f, (len_min, len_mid, len_max)
+ self.uv = data
+
+ f1, lens1, lens1ord = data[0]
+ if data[1]:
+ f2, lens2, lens2ord = data[1]
+ self.width = (lens1[lens1ord[0]] + lens2[lens2ord[0]]) / 2.0
+ self.height = (lens1[lens1ord[1]] + lens2[lens2ord[1]]) / 2.0
+ else: # 1 tri :/
+ self.width = lens1[0]
+ self.height = lens1[1]
+
+ self.children = []
+
+ else: # blender face
+ # self.uv = data.uv
+ self.uv = data.id_data.uv_textures.active.data[data.index].uv # XXX25
+
+ # cos = [v.co for v in data]
+ cos = [data.id_data.vertices[v].co for v in data.vertices] # XXX25
+
+ self.width = ((cos[0] - cos[1]).length + (cos[2] - cos[3]).length) / 2.0
+ self.height = ((cos[1] - cos[2]).length + (cos[0] - cos[3]).length) / 2.0
+
+ self.children = []
+
+ def spin(self):
+ if self.uv and len(self.uv) == 4:
+ self.uv = self.uv[1], self.uv[2], self.uv[3], self.uv[0]
+
+ self.width, self.height = self.height, self.width
+ self.xoff, self.yoff = self.yoff, self.xoff # not needed?
+ self.rot = not self.rot # only for tri pairs.
+ # print("spinning")
+ for pf in self.children:
+ pf.spin()
+
+ def place(self, xoff, yoff, xfac, yfac, margin_w, margin_h):
+
+ xoff += self.xoff
+ yoff += self.yoff
+
+ for pf in self.children:
+ pf.place(xoff, yoff, xfac, yfac, margin_w, margin_h)
+
+ uv = self.uv
+ if not uv:
+ return
+
+ x1 = xoff
+ y1 = yoff
+ x2 = xoff + self.width
+ y2 = yoff + self.height
+
+ # Scale the values
+ x1 = x1 / xfac + margin_w
+ x2 = x2 / xfac - margin_w
+ y1 = y1 / yfac + margin_h
+ y2 = y2 / yfac - margin_h
+
+ # 2 Tri pairs
+ if len(uv) == 2:
+ # match the order of angle sizes of the 3d verts with the UV angles and rotate.
+ def get_tri_angles(v1, v2, v3):
+ a1 = (v2 - v1).angle(v3 - v1, pi)
+ a2 = (v1 - v2).angle(v3 - v2, pi)
+ a3 = pi - (a1 + a2) # a3= (v2 - v3).angle(v1 - v3)
+
+ return [(a1, 0), (a2, 1), (a3, 2)]
+
+ def set_uv(f, p1, p2, p3):
+
+ # cos =
+ #v1 = cos[0]-cos[1]
+ #v2 = cos[1]-cos[2]
+ #v3 = cos[2]-cos[0]
+
+ # angles_co = get_tri_angles(*[v.co for v in f])
+ angles_co = get_tri_angles(*[f.id_data.vertices[v].co for v in f.vertices]) # XXX25
+
+ angles_co.sort()
+ I = [i for a, i in angles_co]
+
+ # fuv = f.uv
+ fuv = f.id_data.uv_textures.active.data[f.index].uv # XXX25
+
+ if self.rot:
+ fuv[I[2]] = p1
+ fuv[I[1]] = p2
+ fuv[I[0]] = p3
+ else:
+ fuv[I[2]] = p1
+ fuv[I[0]] = p2
+ fuv[I[1]] = p3
+
+ f, lens, lensord = uv[0]
+
+ set_uv(f, (x1, y1), (x1, y2 - margin_h), (x2 - margin_w, y1))
+
+ if uv[1]:
+ f, lens, lensord = uv[1]
+ set_uv(f, (x2, y2), (x2, y1 + margin_h), (x1 + margin_w, y2))
+
+ else: # 1 QUAD
+ uv[1][0], uv[1][1] = x1, y1
+ uv[2][0], uv[2][1] = x1, y2
+ uv[3][0], uv[3][1] = x2, y2
+ uv[0][0], uv[0][1] = x2, y1
+
+ def __hash__(self):
+ # None unique hash
+ return self.width, self.height
+
+
+def lightmap_uvpack(meshes,
+ PREF_SEL_ONLY=True,
+ PREF_NEW_UVLAYER=False,
+ PREF_PACK_IN_ONE=False,
+ PREF_APPLY_IMAGE=False,
+ PREF_IMG_PX_SIZE=512,
+ PREF_BOX_DIV=8,
+ PREF_MARGIN_DIV=512
+ ):
+ '''
+ BOX_DIV if the maximum division of the UV map that
+ a box may be consolidated into.
+ Basicly, a lower value will be slower but waist less space
+ and a higher value will have more clumpy boxes but more waisted space
+ '''
+ import time
+
+ if not meshes:
+ return
+
+ t = time.time()
+
+ if PREF_PACK_IN_ONE:
+ if PREF_APPLY_IMAGE:
+ image = bpy.data.images.new(name="lightmap", width=PREF_IMG_PX_SIZE, height=PREF_IMG_PX_SIZE, alpha=False)
+ face_groups = [[]]
+ else:
+ face_groups = []
+
+ for me in meshes:
+ # Add face UV if it does not exist.
+ # All new faces are selected.
+ if not me.uv_textures:
+ me.uv_textures.new()
+
+ if PREF_SEL_ONLY:
+ faces = [f for f in me.faces if f.select]
+ else:
+ faces = me.faces[:]
+
+ if PREF_PACK_IN_ONE:
+ face_groups[0].extend(faces)
+ else:
+ face_groups.append(faces)
+
+ if PREF_NEW_UVLAYER:
+ me.uv_textures.new()
+
+ for face_sel in face_groups:
+ print("\nStarting unwrap")
+
+ if len(face_sel) < 4:
+ print("\tWarning, less then 4 faces, skipping")
+ continue
+
+ pretty_faces = [prettyface(f) for f in face_sel if len(f.vertices) == 4]
+
+ # Do we have any tri's
+ if len(pretty_faces) != len(face_sel):
+
+ # Now add tri's, not so simple because we need to pair them up.
+ def trylens(f):
+ # f must be a tri
+
+ # cos = [v.co for v in f]
+ cos = [f.id_data.vertices[v].co for v in f.vertices] # XXX25
+
+ lens = [(cos[0] - cos[1]).length, (cos[1] - cos[2]).length, (cos[2] - cos[0]).length]
+
+ lens_min = lens.index(min(lens))
+ lens_max = lens.index(max(lens))
+ for i in range(3):
+ if i != lens_min and i != lens_max:
+ lens_mid = i
+ break
+ lens_order = lens_min, lens_mid, lens_max
+
+ return f, lens, lens_order
+
+ tri_lengths = [trylens(f) for f in face_sel if len(f.vertices) == 3]
+ del trylens
+
+ def trilensdiff(t1, t2):
+ return\
+ abs(t1[1][t1[2][0]] - t2[1][t2[2][0]]) + \
+ abs(t1[1][t1[2][1]] - t2[1][t2[2][1]]) + \
+ abs(t1[1][t1[2][2]] - t2[1][t2[2][2]])
+
+ while tri_lengths:
+ tri1 = tri_lengths.pop()
+
+ if not tri_lengths:
+ pretty_faces.append(prettyface((tri1, None)))
+ break
+
+ best_tri_index = -1
+ best_tri_diff = 100000000.0
+
+ for i, tri2 in enumerate(tri_lengths):
+ diff = trilensdiff(tri1, tri2)
+ if diff < best_tri_diff:
+ best_tri_index = i
+ best_tri_diff = diff
+
+ pretty_faces.append(prettyface((tri1, tri_lengths.pop(best_tri_index))))
+
+ # Get the min, max and total areas
+ max_area = 0.0
+ min_area = 100000000.0
+ tot_area = 0
+ for f in face_sel:
+ area = f.area
+ if area > max_area:
+ max_area = area
+ if area < min_area:
+ min_area = area
+ tot_area += area
+
+ max_len = sqrt(max_area)
+ min_len = sqrt(min_area)
+ side_len = sqrt(tot_area)
+
+ # Build widths
+
+ curr_len = max_len
+
+ print("\tGenerating lengths...", end="")
+
+ lengths = []
+ while curr_len > min_len:
+ lengths.append(curr_len)
+ curr_len = curr_len / 2.0
+
+ # Dont allow boxes smaller then the margin
+ # since we contract on the margin, boxes that are smaller will create errors
+ # print(curr_len, side_len/MARGIN_DIV)
+ if curr_len / 4.0 < side_len / PREF_MARGIN_DIV:
+ break
+
+ if not lengths:
+ lengths.append(curr_len)
+
+ # convert into ints
+ lengths_to_ints = {}
+
+ l_int = 1
+ for l in reversed(lengths):
+ lengths_to_ints[l] = l_int
+ l_int *= 2
+
+ lengths_to_ints = list(lengths_to_ints.items())
+ lengths_to_ints.sort()
+ print("done")
+
+ # apply quantized values.
+
+ for pf in pretty_faces:
+ w = pf.width
+ h = pf.height
+ bestw_diff = 1000000000.0
+ besth_diff = 1000000000.0
+ new_w = 0.0
+ new_h = 0.0
+ for l, i in lengths_to_ints:
+ d = abs(l - w)
+ if d < bestw_diff:
+ bestw_diff = d
+ new_w = i # assign the int version
+
+ d = abs(l - h)
+ if d < besth_diff:
+ besth_diff = d
+ new_h = i # ditto
+
+ pf.width = new_w
+ pf.height = new_h
+
+ if new_w > new_h:
+ pf.spin()
+
+ print("...done")
+
+ # Since the boxes are sized in powers of 2, we can neatly group them into bigger squares
+ # this is done hierarchily, so that we may avoid running the pack function
+ # on many thousands of boxes, (under 1k is best) because it would get slow.
+ # Using an off and even dict us usefull because they are packed differently
+ # where w/h are the same, their packed in groups of 4
+ # where they are different they are packed in pairs
+ #
+ # After this is done an external pack func is done that packs the whole group.
+
+ print("\tConsolidating Boxes...", end="")
+ even_dict = {} # w/h are the same, the key is an int (w)
+ odd_dict = {} # w/h are different, the key is the (w,h)
+
+ for pf in pretty_faces:
+ w, h = pf.width, pf.height
+ if w == h:
+ even_dict.setdefault(w, []).append(pf)
+ else:
+ odd_dict.setdefault((w, h), []).append(pf)
+
+ # Count the number of boxes consolidated, only used for stats.
+ c = 0
+
+ # This is tricky. the total area of all packed boxes, then squt that to get an estimated size
+ # this is used then converted into out INT space so we can compare it with
+ # the ints assigned to the boxes size
+ # and divided by BOX_DIV, basicly if BOX_DIV is 8
+ # ...then the maximum box consolidataion (recursive grouping) will have a max width & height
+ # ...1/8th of the UV size.
+ # ...limiting this is needed or you end up with bug unused texture spaces
+ # ...however if its too high, boxpacking is way too slow for high poly meshes.
+ float_to_int_factor = lengths_to_ints[0][0]
+ if float_to_int_factor > 0:
+ max_int_dimension = int(((side_len / float_to_int_factor)) / PREF_BOX_DIV)
+ ok = True
+ else:
+ max_int_dimension = 0.0 # wont be used
+ ok = False
+
+ # RECURSIVE prettyface grouping
+ while ok:
+ ok = False
+
+ # Tall boxes in groups of 2
+ for d, boxes in odd_dict.items():
+ if d[1] < max_int_dimension:
+ #\boxes.sort(key = lambda a: len(a.children))
+ while len(boxes) >= 2:
+ # print("foo", len(boxes))
+ ok = True
+ c += 1
+ pf_parent = prettyface([boxes.pop(), boxes.pop()])
+ pretty_faces.append(pf_parent)
+
+ w, h = pf_parent.width, pf_parent.height
+
+ if w > h:
+ raise "error"
+
+ if w == h:
+ even_dict.setdefault(w, []).append(pf_parent)
+ else:
+ odd_dict.setdefault((w, h), []).append(pf_parent)
+
+ # Even boxes in groups of 4
+ for d, boxes in even_dict.items():
+ if d < max_int_dimension:
+ boxes.sort(key=lambda a: len(a.children))
+
+ while len(boxes) >= 4:
+ # print("bar", len(boxes))
+ ok = True
+ c += 1
+
+ pf_parent = prettyface([boxes.pop(), boxes.pop(), boxes.pop(), boxes.pop()])
+ pretty_faces.append(pf_parent)
+ w = pf_parent.width # width and weight are the same
+ even_dict.setdefault(w, []).append(pf_parent)
+
+ del even_dict
+ del odd_dict
+
+ orig = len(pretty_faces)
+
+ pretty_faces = [pf for pf in pretty_faces if not pf.has_parent]
+
+ # spin every second prettyface
+ # if there all vertical you get less efficiently used texture space
+ i = len(pretty_faces)
+ d = 0
+ while i:
+ i -= 1
+ pf = pretty_faces[i]
+ if pf.width != pf.height:
+ d += 1
+ if d % 2: # only pack every second
+ pf.spin()
+ # pass
+
+ print("Consolidated", c, "boxes, done")
+ # print("done", orig, len(pretty_faces))
+
+ # boxes2Pack.append([islandIdx, w,h])
+ print("\tPacking Boxes", len(pretty_faces), end="...")
+ boxes2Pack = [[0.0, 0.0, pf.width, pf.height, i] for i, pf in enumerate(pretty_faces)]
+ packWidth, packHeight = mathutils.geometry.box_pack_2d(boxes2Pack)
+
+ # print(packWidth, packHeight)
+
+ packWidth = float(packWidth)
+ packHeight = float(packHeight)
+
+ margin_w = ((packWidth) / PREF_MARGIN_DIV) / packWidth
+ margin_h = ((packHeight) / PREF_MARGIN_DIV) / packHeight
+
+ # print(margin_w, margin_h)
+ print("done")
+
+ # Apply the boxes back to the UV coords.
+ print("\twriting back UVs", end="")
+ for i, box in enumerate(boxes2Pack):
+ pretty_faces[i].place(box[0], box[1], packWidth, packHeight, margin_w, margin_h)
+ # pf.place(box[1][1], box[1][2], packWidth, packHeight, margin_w, margin_h)
+ print("done")
+
+ if PREF_APPLY_IMAGE:
+ if not PREF_PACK_IN_ONE:
+ image = Image.New("lightmap", PREF_IMG_PX_SIZE, PREF_IMG_PX_SIZE, 24)
+
+ for f in face_sel:
+ # f.image = image
+ f.id_data.uv_textures.active.data[f.index].image = image # XXX25
+
+ for me in meshes:
+ me.update()
+
+ print("finished all %.2f " % (time.time() - t))
+
+ # Window.RedrawAll()
+
+
+def unwrap(operator, context, **kwargs):
+
+ is_editmode = (bpy.context.object.mode == 'EDIT')
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+
+ PREF_ACT_ONLY = kwargs.pop("PREF_ACT_ONLY")
+
+ meshes = []
+ if PREF_ACT_ONLY:
+ obj = context.scene.objects.active
+ if obj and obj.type == 'MESH':
+ meshes = [obj.data]
+ else:
+ meshes = {me.name: me for obj in context.selected_objects if obj.type == 'MESH' for me in (obj.data,) if not me.library if len(me.faces)}.values()
+
+ if not meshes:
+ operator.report({'ERROR'}, "No mesh object.")
+ return {'CANCELLED'}
+
+ lightmap_uvpack(meshes, **kwargs)
+
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+
+ return {'FINISHED'}
+
+from bpy.props import BoolProperty, FloatProperty, IntProperty, EnumProperty
+
+
+class LightMapPack(bpy.types.Operator):
+ '''Follow UVs from active quads along continuous face loops'''
+ bl_idname = "uv.lightmap_pack"
+ bl_label = "Lightmap Pack"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ PREF_CONTEXT = bpy.props.EnumProperty(
+ items=(("SEL_FACES", "Selected Faces", "Space all UVs evently"),
+ ("ALL_FACES", "All Faces", "Average space UVs edge length of each loop"),
+ ("ALL_OBJECTS", "Selected Mesh Object", "Average space UVs edge length of each loop")
+ ),
+ name="Selection",
+ description="")
+
+ # Image & UVs...
+ PREF_PACK_IN_ONE = BoolProperty(name="Share Tex Space", default=True, description="Objects Share texture space, map all objects into 1 uvmap")
+ PREF_NEW_UVLAYER = BoolProperty(name="New UV Layer", default=False, description="Create a new UV layer for every mesh packed")
+ PREF_APPLY_IMAGE = BoolProperty(name="New Image", default=False, description="Assign new images for every mesh (only one if shared tex space enabled)")
+ PREF_IMG_PX_SIZE = IntProperty(name="Image Size", min=64, max=5000, default=512, description="Width and Height for the new image")
+
+ # UV Packing...
+ PREF_BOX_DIV = IntProperty(name="Pack Quality", min=1, max=48, default=12, description="Pre Packing before the complex boxpack")
+ PREF_MARGIN_DIV = FloatProperty(name="Margin", min=0.001, max=1.0, default=0.1, description="Size of the margin as a division of the UV")
+
+ def execute(self, context):
+ kwargs = self.as_keywords()
+ PREF_CONTEXT = kwargs.pop("PREF_CONTEXT")
+
+ if PREF_CONTEXT == 'SEL_FACES':
+ kwargs["PREF_ACT_ONLY"] = True
+ kwargs["PREF_SEL_ONLY"] = True
+ elif PREF_CONTEXT == 'ALL_FACES':
+ kwargs["PREF_ACT_ONLY"] = True
+ kwargs["PREF_SEL_ONLY"] = False
+ elif PREF_CONTEXT == 'ALL_OBJECTS':
+ kwargs["PREF_ACT_ONLY"] = False
+ kwargs["PREF_SEL_ONLY"] = False
+ else:
+ raise Exception("invalid context")
+
+ kwargs["PREF_MARGIN_DIV"] = int(1.0 / (kwargs["PREF_MARGIN_DIV"] / 100.0))
+
+ return unwrap(self, context, **kwargs)
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self)
diff --git a/release/scripts/startup/bl_operators/uvcalc_smart_project.py b/release/scripts/startup/bl_operators/uvcalc_smart_project.py
new file mode 100644
index 00000000000..4f5b1d8b233
--- /dev/null
+++ b/release/scripts/startup/bl_operators/uvcalc_smart_project.py
@@ -0,0 +1,1141 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+from mathutils import Matrix, Vector, geometry
+import bpy
+
+DEG_TO_RAD = 0.017453292519943295 # pi/180.0
+SMALL_NUM = 0.000000001
+BIG_NUM = 1e15
+
+global USER_FILL_HOLES
+global USER_FILL_HOLES_QUALITY
+USER_FILL_HOLES = None
+USER_FILL_HOLES_QUALITY = None
+
+def pointInTri2D(v, v1, v2, v3):
+ key = v1.x, v1.y, v2.x, v2.y, v3.x, v3.y
+
+ # Commented because its slower to do the bounds check, we should realy cache the bounds info for each face.
+ '''
+ # BOUNDS CHECK
+ xmin= 1000000
+ ymin= 1000000
+
+ xmax= -1000000
+ ymax= -1000000
+
+ for i in (0,2,4):
+ x= key[i]
+ y= key[i+1]
+
+ if xmax<x: xmax= x
+ if ymax<y: ymax= y
+ if xmin>x: xmin= x
+ if ymin>y: ymin= y
+
+ x= v.x
+ y= v.y
+
+ if x<xmin or x>xmax or y < ymin or y > ymax:
+ return False
+ # Done with bounds check
+ '''
+ try:
+ mtx = dict_matrix[key]
+ if not mtx:
+ return False
+ except:
+ side1 = v2 - v1
+ side2 = v3 - v1
+
+ nor = side1.cross(side2)
+
+ mtx = Matrix((side1, side2, nor))
+
+ # Zero area 2d tri, even tho we throw away zerop area faces
+ # the projection UV can result in a zero area UV.
+ if not mtx.determinant():
+ dict_matrix[key] = None
+ return False
+
+ mtx.invert()
+
+ dict_matrix[key] = mtx
+
+ uvw = (v - v1) * mtx
+ return 0 <= uvw[0] and 0 <= uvw[1] and uvw[0] + uvw[1] <= 1
+
+
+def boundsIsland(faces):
+ minx = maxx = faces[0].uv[0][0] # Set initial bounds.
+ miny = maxy = faces[0].uv[0][1]
+ # print len(faces), minx, maxx, miny , maxy
+ for f in faces:
+ for uv in f.uv:
+ x= uv.x
+ y= uv.y
+ if x<minx: minx= x
+ if y<miny: miny= y
+ if x>maxx: maxx= x
+ if y>maxy: maxy= y
+
+ return minx, miny, maxx, maxy
+
+"""
+def boundsEdgeLoop(edges):
+ minx = maxx = edges[0][0] # Set initial bounds.
+ miny = maxy = edges[0][1]
+ # print len(faces), minx, maxx, miny , maxy
+ for ed in edges:
+ for pt in ed:
+ print 'ass'
+ x= pt[0]
+ y= pt[1]
+ if x<minx: x= minx
+ if y<miny: y= miny
+ if x>maxx: x= maxx
+ if y>maxy: y= maxy
+
+ return minx, miny, maxx, maxy
+"""
+
+# Turns the islands into a list of unpordered edges (Non internal)
+# Onlt for UV's
+# only returns outline edges for intersection tests. and unique points.
+
+def island2Edge(island):
+
+ # Vert index edges
+ edges = {}
+
+ unique_points= {}
+
+ for f in island:
+ f_uvkey= map(tuple, f.uv)
+
+
+ for vIdx, edkey in enumerate(f.edge_keys):
+ unique_points[f_uvkey[vIdx]] = f.uv[vIdx]
+
+ if f.v[vIdx].index > f.v[vIdx-1].index:
+ i1= vIdx-1; i2= vIdx
+ else:
+ i1= vIdx; i2= vIdx-1
+
+ try: edges[ f_uvkey[i1], f_uvkey[i2] ] *= 0 # sets eny edge with more then 1 user to 0 are not returned.
+ except: edges[ f_uvkey[i1], f_uvkey[i2] ] = (f.uv[i1] - f.uv[i2]).length,
+
+ # If 2 are the same then they will be together, but full [a,b] order is not correct.
+
+ # Sort by length
+
+
+ length_sorted_edges = [(Vector(key[0]), Vector(key[1]), value) for key, value in edges.items() if value != 0]
+
+ try: length_sorted_edges.sort(key = lambda A: -A[2]) # largest first
+ except: length_sorted_edges.sort(lambda A, B: cmp(B[2], A[2]))
+
+ # Its okay to leave the length in there.
+ #for e in length_sorted_edges:
+ # e.pop(2)
+
+ # return edges and unique points
+ return length_sorted_edges, [v.to_3d() for v in unique_points.values()]
+
+# ========================= NOT WORKING????
+# Find if a points inside an edge loop, un-orderd.
+# pt is and x/y
+# edges are a non ordered loop of edges.
+# #offsets are the edge x and y offset.
+"""
+def pointInEdges(pt, edges):
+ #
+ x1 = pt[0]
+ y1 = pt[1]
+
+ # Point to the left of this line.
+ x2 = -100000
+ y2 = -10000
+ intersectCount = 0
+ for ed in edges:
+ xi, yi = lineIntersection2D(x1,y1, x2,y2, ed[0][0], ed[0][1], ed[1][0], ed[1][1])
+ if xi != None: # Is there an intersection.
+ intersectCount+=1
+
+ return intersectCount % 2
+"""
+
+def pointInIsland(pt, island):
+ vec1, vec2, vec3 = Vector(), Vector(), Vector()
+ for f in island:
+ vec1.x, vec1.y = f.uv[0]
+ vec2.x, vec2.y = f.uv[1]
+ vec3.x, vec3.y = f.uv[2]
+
+ if pointInTri2D(pt, vec1, vec2, vec3):
+ return True
+
+ if len(f.v) == 4:
+ vec1.x, vec1.y = f.uv[0]
+ vec2.x, vec2.y = f.uv[2]
+ vec3.x, vec3.y = f.uv[3]
+ if pointInTri2D(pt, vec1, vec2, vec3):
+ return True
+ return False
+
+
+# box is (left,bottom, right, top)
+def islandIntersectUvIsland(source, target, SourceOffset):
+ # Is 1 point in the box, inside the vertLoops
+ edgeLoopsSource = source[6] # Pretend this is offset
+ edgeLoopsTarget = target[6]
+
+ # Edge intersect test
+ for ed in edgeLoopsSource:
+ for seg in edgeLoopsTarget:
+ i = geometry.intersect_line_line_2d(\
+ seg[0], seg[1], SourceOffset+ed[0], SourceOffset+ed[1])
+ if i:
+ return 1 # LINE INTERSECTION
+
+ # 1 test for source being totally inside target
+ SourceOffset.resize_3d()
+ for pv in source[7]:
+ if pointInIsland(pv+SourceOffset, target[0]):
+ return 2 # SOURCE INSIDE TARGET
+
+ # 2 test for a part of the target being totaly inside the source.
+ for pv in target[7]:
+ if pointInIsland(pv-SourceOffset, source[0]):
+ return 3 # PART OF TARGET INSIDE SOURCE.
+
+ return 0 # NO INTERSECTION
+
+
+
+
+# Returns the X/y Bounds of a list of vectors.
+def testNewVecLs2DRotIsBetter(vecs, mat=-1, bestAreaSoFar = -1):
+
+ # UV's will never extend this far.
+ minx = miny = BIG_NUM
+ maxx = maxy = -BIG_NUM
+
+ for i, v in enumerate(vecs):
+
+ # Do this allong the way
+ if mat != -1:
+ v = vecs[i] = v*mat
+ x= v.x
+ y= v.y
+ if x<minx: minx= x
+ if y<miny: miny= y
+ if x>maxx: maxx= x
+ if y>maxy: maxy= y
+
+ # Spesific to this algo, bail out if we get bigger then the current area
+ if bestAreaSoFar != -1 and (maxx-minx) * (maxy-miny) > bestAreaSoFar:
+ return (BIG_NUM, None), None
+ w = maxx-minx
+ h = maxy-miny
+ return (w*h, w,h), vecs # Area, vecs
+
+def optiRotateUvIsland(faces):
+ global currentArea
+
+ # Bestfit Rotation
+ def best2dRotation(uvVecs, MAT1, MAT2):
+ global currentArea
+
+ newAreaPos, newfaceProjectionGroupListPos =\
+ testNewVecLs2DRotIsBetter(uvVecs[:], MAT1, currentArea[0])
+
+
+ # Why do I use newpos here? May as well give the best area to date for an early bailout
+ # some slight speed increase in this.
+ # If the new rotation is smaller then the existing, we can
+ # avoid copying a list and overwrite the old, crappy one.
+
+ if newAreaPos[0] < currentArea[0]:
+ newAreaNeg, newfaceProjectionGroupListNeg =\
+ testNewVecLs2DRotIsBetter(uvVecs, MAT2, newAreaPos[0]) # Reuse the old bigger list.
+ else:
+ newAreaNeg, newfaceProjectionGroupListNeg =\
+ testNewVecLs2DRotIsBetter(uvVecs[:], MAT2, currentArea[0]) # Cant reuse, make a copy.
+
+
+ # Now from the 3 options we need to discover which to use
+ # we have cerrentArea/newAreaPos/newAreaNeg
+ bestArea = min(currentArea[0], newAreaPos[0], newAreaNeg[0])
+
+ if currentArea[0] == bestArea:
+ return uvVecs
+ elif newAreaPos[0] == bestArea:
+ uvVecs = newfaceProjectionGroupListPos
+ currentArea = newAreaPos
+ elif newAreaNeg[0] == bestArea:
+ uvVecs = newfaceProjectionGroupListNeg
+ currentArea = newAreaNeg
+
+ return uvVecs
+
+
+ # Serialized UV coords to Vectors
+ uvVecs = [uv for f in faces for uv in f.uv]
+
+ # Theres a small enough number of these to hard code it
+ # rather then a loop.
+
+ # Will not modify anything
+ currentArea, dummy =\
+ testNewVecLs2DRotIsBetter(uvVecs)
+
+
+ # Try a 45d rotation
+ newAreaPos, newfaceProjectionGroupListPos = testNewVecLs2DRotIsBetter(uvVecs[:], ROTMAT_2D_POS_45D, currentArea[0])
+
+ if newAreaPos[0] < currentArea[0]:
+ uvVecs = newfaceProjectionGroupListPos
+ currentArea = newAreaPos
+ # 45d done
+
+ # Testcase different rotations and find the onfe that best fits in a square
+ for ROTMAT in RotMatStepRotation:
+ uvVecs = best2dRotation(uvVecs, ROTMAT[0], ROTMAT[1])
+
+ # Only if you want it, make faces verticle!
+ if currentArea[1] > currentArea[2]:
+ # Rotate 90d
+ # Work directly on the list, no need to return a value.
+ testNewVecLs2DRotIsBetter(uvVecs, ROTMAT_2D_POS_90D)
+
+
+ # Now write the vectors back to the face UV's
+ i = 0 # count the serialized uv/vectors
+ for f in faces:
+ #f.uv = [uv for uv in uvVecs[i:len(f)+i] ]
+ for j, k in enumerate(range(i, len(f.v)+i)):
+ f.uv[j][:] = uvVecs[k]
+ i += len(f.v)
+
+
+# Takes an island list and tries to find concave, hollow areas to pack smaller islands into.
+def mergeUvIslands(islandList):
+ global USER_FILL_HOLES
+ global USER_FILL_HOLES_QUALITY
+
+
+ # Pack islands to bottom LHS
+ # Sync with island
+
+ #islandTotFaceArea = [] # A list of floats, each island area
+ #islandArea = [] # a list of tuples ( area, w,h)
+
+
+ decoratedIslandList = []
+
+ islandIdx = len(islandList)
+ while islandIdx:
+ islandIdx-=1
+ minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
+ w, h = maxx-minx, maxy-miny
+
+ totFaceArea = 0
+ offset= Vector((minx, miny))
+ for f in islandList[islandIdx]:
+ for uv in f.uv:
+ uv -= offset
+
+ totFaceArea += f.area
+
+ islandBoundsArea = w*h
+ efficiency = abs(islandBoundsArea - totFaceArea)
+
+ # UV Edge list used for intersections as well as unique points.
+ edges, uniqueEdgePoints = island2Edge(islandList[islandIdx])
+
+ decoratedIslandList.append([islandList[islandIdx], totFaceArea, efficiency, islandBoundsArea, w,h, edges, uniqueEdgePoints])
+
+
+ # Sort by island bounding box area, smallest face area first.
+ # no.. chance that to most simple edge loop first.
+ decoratedIslandListAreaSort =decoratedIslandList[:]
+
+ decoratedIslandListAreaSort.sort(key = lambda A: A[3])
+
+ # sort by efficiency, Least Efficient first.
+ decoratedIslandListEfficSort = decoratedIslandList[:]
+ # decoratedIslandListEfficSort.sort(lambda A, B: cmp(B[2], A[2]))
+
+ decoratedIslandListEfficSort.sort(key = lambda A: -A[2])
+
+ # ================================================== THESE CAN BE TWEAKED.
+ # This is a quality value for the number of tests.
+ # from 1 to 4, generic quality value is from 1 to 100
+ USER_STEP_QUALITY = ((USER_FILL_HOLES_QUALITY - 1) / 25.0) + 1
+
+ # If 100 will test as long as there is enough free space.
+ # this is rarely enough, and testing takes a while, so lower quality speeds this up.
+
+ # 1 means they have the same quality
+ USER_FREE_SPACE_TO_TEST_QUALITY = 1 + (((100 - USER_FILL_HOLES_QUALITY)/100.0) *5)
+
+ #print 'USER_STEP_QUALITY', USER_STEP_QUALITY
+ #print 'USER_FREE_SPACE_TO_TEST_QUALITY', USER_FREE_SPACE_TO_TEST_QUALITY
+
+ removedCount = 0
+
+ areaIslandIdx = 0
+ ctrl = Window.Qual.CTRL
+ BREAK= False
+ while areaIslandIdx < len(decoratedIslandListAreaSort) and not BREAK:
+ sourceIsland = decoratedIslandListAreaSort[areaIslandIdx]
+ # Alredy packed?
+ if not sourceIsland[0]:
+ areaIslandIdx+=1
+ else:
+ efficIslandIdx = 0
+ while efficIslandIdx < len(decoratedIslandListEfficSort) and not BREAK:
+
+ if Window.GetKeyQualifiers() & ctrl:
+ BREAK= True
+ break
+
+ # Now we have 2 islands, is the efficience of the islands lowers theres an
+ # increasing likely hood that we can fit merge into the bigger UV island.
+ # this ensures a tight fit.
+
+ # Just use figures we have about user/unused area to see if they might fit.
+
+ targetIsland = decoratedIslandListEfficSort[efficIslandIdx]
+
+
+ if sourceIsland[0] == targetIsland[0] or\
+ not targetIsland[0] or\
+ not sourceIsland[0]:
+ pass
+ else:
+
+ # ([island, totFaceArea, efficiency, islandArea, w,h])
+ # Waisted space on target is greater then UV bounding island area.
+
+
+ # if targetIsland[3] > (sourceIsland[2]) and\ #
+ # print USER_FREE_SPACE_TO_TEST_QUALITY
+ if targetIsland[2] > (sourceIsland[1] * USER_FREE_SPACE_TO_TEST_QUALITY) and\
+ targetIsland[4] > sourceIsland[4] and\
+ targetIsland[5] > sourceIsland[5]:
+
+ # DEBUG # print '%.10f %.10f' % (targetIsland[3], sourceIsland[1])
+
+ # These enough spare space lets move the box until it fits
+
+ # How many times does the source fit into the target x/y
+ blockTestXUnit = targetIsland[4]/sourceIsland[4]
+ blockTestYUnit = targetIsland[5]/sourceIsland[5]
+
+ boxLeft = 0
+
+
+ # Distllllance we can move between whilst staying inside the targets bounds.
+ testWidth = targetIsland[4] - sourceIsland[4]
+ testHeight = targetIsland[5] - sourceIsland[5]
+
+ # Increment we move each test. x/y
+ xIncrement = (testWidth / (blockTestXUnit * ((USER_STEP_QUALITY/50)+0.1)))
+ yIncrement = (testHeight / (blockTestYUnit * ((USER_STEP_QUALITY/50)+0.1)))
+
+ # Make sure were not moving less then a 3rg of our width/height
+ if xIncrement<sourceIsland[4]/3:
+ xIncrement= sourceIsland[4]
+ if yIncrement<sourceIsland[5]/3:
+ yIncrement= sourceIsland[5]
+
+
+ boxLeft = 0 # Start 1 back so we can jump into the loop.
+ boxBottom= 0 #-yIncrement
+
+ ##testcount= 0
+
+ while boxBottom <= testHeight:
+ # Should we use this? - not needed for now.
+ #if Window.GetKeyQualifiers() & ctrl:
+ # BREAK= True
+ # break
+
+ ##testcount+=1
+ #print 'Testing intersect'
+ Intersect = islandIntersectUvIsland(sourceIsland, targetIsland, Vector((boxLeft, boxBottom)))
+ #print 'Done', Intersect
+ if Intersect == 1: # Line intersect, dont bother with this any more
+ pass
+
+ if Intersect == 2: # Source inside target
+ '''
+ We have an intersection, if we are inside the target
+ then move us 1 whole width accross,
+ Its possible this is a bad idea since 2 skinny Angular faces
+ could join without 1 whole move, but its a lot more optimal to speed this up
+ since we have already tested for it.
+
+ It gives about 10% speedup with minimal errors.
+ '''
+ #print 'ass'
+ # Move the test allong its width + SMALL_NUM
+ #boxLeft += sourceIsland[4] + SMALL_NUM
+ boxLeft += sourceIsland[4]
+ elif Intersect == 0: # No intersection?? Place it.
+ # Progress
+ removedCount +=1
+#XXX Window.DrawProgressBar(0.0, 'Merged: %i islands, Ctrl to finish early.' % removedCount)
+
+ # Move faces into new island and offset
+ targetIsland[0].extend(sourceIsland[0])
+ offset= Vector((boxLeft, boxBottom))
+
+ for f in sourceIsland[0]:
+ for uv in f.uv:
+ uv+= offset
+
+ sourceIsland[0][:] = [] # Empty
+
+
+ # Move edge loop into new and offset.
+ # targetIsland[6].extend(sourceIsland[6])
+ #while sourceIsland[6]:
+ targetIsland[6].extend( [ (\
+ (e[0]+offset, e[1]+offset, e[2])\
+ ) for e in sourceIsland[6] ] )
+
+ sourceIsland[6][:] = [] # Empty
+
+ # Sort by edge length, reverse so biggest are first.
+
+ try: targetIsland[6].sort(key = lambda A: A[2])
+ except: targetIsland[6].sort(lambda B,A: cmp(A[2], B[2] ))
+
+
+ targetIsland[7].extend(sourceIsland[7])
+ offset= Vector((boxLeft, boxBottom, 0.0))
+ for p in sourceIsland[7]:
+ p+= offset
+
+ sourceIsland[7][:] = []
+
+
+ # Decrement the efficiency
+ targetIsland[1]+=sourceIsland[1] # Increment totFaceArea
+ targetIsland[2]-=sourceIsland[1] # Decrement efficiency
+ # IF we ever used these again, should set to 0, eg
+ sourceIsland[2] = 0 # No area if anyone wants to know
+
+ break
+
+
+ # INCREMENR NEXT LOCATION
+ if boxLeft > testWidth:
+ boxBottom += yIncrement
+ boxLeft = 0.0
+ else:
+ boxLeft += xIncrement
+ ##print testcount
+
+ efficIslandIdx+=1
+ areaIslandIdx+=1
+
+ # Remove empty islands
+ i = len(islandList)
+ while i:
+ i-=1
+ if not islandList[i]:
+ del islandList[i] # Can increment islands removed here.
+
+# Takes groups of faces. assumes face groups are UV groups.
+def getUvIslands(faceGroups, me):
+
+ # Get seams so we dont cross over seams
+ edge_seams = {} # shoudl be a set
+ for ed in me.edges:
+ if ed.use_seam:
+ edge_seams[ed.key] = None # dummy var- use sets!
+ # Done finding seams
+
+
+ islandList = []
+
+#XXX Window.DrawProgressBar(0.0, 'Splitting %d projection groups into UV islands:' % len(faceGroups))
+ #print '\tSplitting %d projection groups into UV islands:' % len(faceGroups),
+ # Find grouped faces
+
+ faceGroupIdx = len(faceGroups)
+
+ while faceGroupIdx:
+ faceGroupIdx-=1
+ faces = faceGroups[faceGroupIdx]
+
+ if not faces:
+ continue
+
+ # Build edge dict
+ edge_users = {}
+
+ for i, f in enumerate(faces):
+ for ed_key in f.edge_keys:
+ if ed_key in edge_seams: # DELIMIT SEAMS! ;)
+ edge_users[ed_key] = [] # so as not to raise an error
+ else:
+ try: edge_users[ed_key].append(i)
+ except: edge_users[ed_key] = [i]
+
+ # Modes
+ # 0 - face not yet touched.
+ # 1 - added to island list, and need to search
+ # 2 - touched and searched - dont touch again.
+ face_modes = [0] * len(faces) # initialize zero - untested.
+
+ face_modes[0] = 1 # start the search with face 1
+
+ newIsland = []
+
+ newIsland.append(faces[0])
+
+
+ ok = True
+ while ok:
+
+ ok = True
+ while ok:
+ ok= False
+ for i in range(len(faces)):
+ if face_modes[i] == 1: # search
+ for ed_key in faces[i].edge_keys:
+ for ii in edge_users[ed_key]:
+ if i != ii and face_modes[ii] == 0:
+ face_modes[ii] = ok = 1 # mark as searched
+ newIsland.append(faces[ii])
+
+ # mark as searched, dont look again.
+ face_modes[i] = 2
+
+ islandList.append(newIsland)
+
+ ok = False
+ for i in range(len(faces)):
+ if face_modes[i] == 0:
+ newIsland = []
+ newIsland.append(faces[i])
+
+ face_modes[i] = ok = 1
+ break
+ # if not ok will stop looping
+
+#XXX Window.DrawProgressBar(0.1, 'Optimizing Rotation for %i UV Islands' % len(islandList))
+
+ for island in islandList:
+ optiRotateUvIsland(island)
+
+ return islandList
+
+
+def packIslands(islandList):
+ if USER_FILL_HOLES:
+#XXX Window.DrawProgressBar(0.1, 'Merging Islands (Ctrl: skip merge)...')
+ mergeUvIslands(islandList) # Modify in place
+
+
+ # Now we have UV islands, we need to pack them.
+
+ # Make a synchronised list with the islands
+ # so we can box pak the islands.
+ packBoxes = []
+
+ # Keep a list of X/Y offset so we can save time by writing the
+ # uv's and packed data in one pass.
+ islandOffsetList = []
+
+ islandIdx = 0
+
+ while islandIdx < len(islandList):
+ minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
+
+ w, h = maxx-minx, maxy-miny
+
+ if USER_ISLAND_MARGIN:
+ minx -= USER_ISLAND_MARGIN# *w
+ miny -= USER_ISLAND_MARGIN# *h
+ maxx += USER_ISLAND_MARGIN# *w
+ maxy += USER_ISLAND_MARGIN# *h
+
+ # recalc width and height
+ w, h = maxx-minx, maxy-miny
+
+ if w < 0.00001 or h < 0.00001:
+ del islandList[islandIdx]
+ islandIdx -=1
+ continue
+
+ '''Save the offset to be applied later,
+ we could apply to the UVs now and allign them to the bottom left hand area
+ of the UV coords like the box packer imagines they are
+ but, its quicker just to remember their offset and
+ apply the packing and offset in 1 pass '''
+ islandOffsetList.append((minx, miny))
+
+ # Add to boxList. use the island idx for the BOX id.
+ packBoxes.append([0, 0, w, h])
+ islandIdx+=1
+
+ # Now we have a list of boxes to pack that syncs
+ # with the islands.
+
+ #print '\tPacking UV Islands...'
+#XXX Window.DrawProgressBar(0.7, 'Packing %i UV Islands...' % len(packBoxes) )
+
+ # time1 = time.time()
+ packWidth, packHeight = geometry.box_pack_2d(packBoxes)
+
+ # print 'Box Packing Time:', time.time() - time1
+
+ #if len(pa ckedLs) != len(islandList):
+ # raise "Error packed boxes differes from original length"
+
+ #print '\tWriting Packed Data to faces'
+#XXX Window.DrawProgressBar(0.8, 'Writing Packed Data to faces')
+
+ # Sort by ID, so there in sync again
+ islandIdx = len(islandList)
+ # Having these here avoids devide by 0
+ if islandIdx:
+
+ if USER_STRETCH_ASPECT:
+ # Maximize to uv area?? Will write a normalize function.
+ xfactor = 1.0 / packWidth
+ yfactor = 1.0 / packHeight
+ else:
+ # Keep proportions.
+ xfactor = yfactor = 1.0 / max(packWidth, packHeight)
+
+ while islandIdx:
+ islandIdx -=1
+ # Write the packed values to the UV's
+
+ xoffset = packBoxes[islandIdx][0] - islandOffsetList[islandIdx][0]
+ yoffset = packBoxes[islandIdx][1] - islandOffsetList[islandIdx][1]
+
+ for f in islandList[islandIdx]: # Offsetting the UV's so they fit in there packed box
+ for uv in f.uv:
+ uv.x= (uv.x+xoffset) * xfactor
+ uv.y= (uv.y+yoffset) * yfactor
+
+
+
+def VectoQuat(vec):
+ vec = vec.normalized()
+ if abs(vec.x) > 0.5:
+ return vec.to_track_quat('Z', 'X')
+ else:
+ return vec.to_track_quat('Z', 'Y')
+
+
+class thickface(object):
+ __slost__= 'v', 'uv', 'no', 'area', 'edge_keys'
+ def __init__(self, face, uvface, mesh_verts):
+ self.v = [mesh_verts[i] for i in face.vertices]
+ if len(self.v)==4:
+ self.uv = uvface.uv1, uvface.uv2, uvface.uv3, uvface.uv4
+ else:
+ self.uv = uvface.uv1, uvface.uv2, uvface.uv3
+
+ self.no = face.normal
+ self.area = face.area
+ self.edge_keys = face.edge_keys
+
+
+def main_consts():
+ from math import radians
+
+ global ROTMAT_2D_POS_90D
+ global ROTMAT_2D_POS_45D
+ global RotMatStepRotation
+
+ ROTMAT_2D_POS_90D = Matrix.Rotation( radians(90.0), 2)
+ ROTMAT_2D_POS_45D = Matrix.Rotation( radians(45.0), 2)
+
+ RotMatStepRotation = []
+ rot_angle = 22.5 #45.0/2
+ while rot_angle > 0.1:
+ RotMatStepRotation.append([\
+ Matrix.Rotation( radians(rot_angle), 2),\
+ Matrix.Rotation( radians(-rot_angle), 2)])
+
+ rot_angle = rot_angle/2.0
+
+
+global ob
+ob = None
+def main(context, island_margin, projection_limit):
+ global USER_FILL_HOLES
+ global USER_FILL_HOLES_QUALITY
+ global USER_STRETCH_ASPECT
+ global USER_ISLAND_MARGIN
+
+ from math import cos
+ import time
+
+ global dict_matrix
+ dict_matrix = {}
+
+
+ # Constants:
+ # Takes a list of faces that make up a UV island and rotate
+ # until they optimally fit inside a square.
+ global ROTMAT_2D_POS_90D
+ global ROTMAT_2D_POS_45D
+ global RotMatStepRotation
+ main_consts()
+
+#XXX objects= bpy.data.scenes.active.objects
+ objects = context.selected_editable_objects
+
+
+ # we can will tag them later.
+ obList = [ob for ob in objects if ob.type == 'MESH']
+
+ # Face select object may not be selected.
+#XXX ob = objects.active
+ ob= objects[0]
+
+ if ob and (not ob.select) and ob.type == 'MESH':
+ # Add to the list
+ obList =[ob]
+ del objects
+
+ if not obList:
+ raise('error, no selected mesh objects')
+
+ # Create the variables.
+ USER_PROJECTION_LIMIT = projection_limit
+ USER_ONLY_SELECTED_FACES = (1)
+ USER_SHARE_SPACE = (1) # Only for hole filling.
+ USER_STRETCH_ASPECT = (1) # Only for hole filling.
+ USER_ISLAND_MARGIN = island_margin # Only for hole filling.
+ USER_FILL_HOLES = (0)
+ USER_FILL_HOLES_QUALITY = (50) # Only for hole filling.
+ USER_VIEW_INIT = (0) # Only for hole filling.
+ USER_AREA_WEIGHT = (1) # Only for hole filling.
+
+ # Reuse variable
+ if len(obList) == 1:
+ ob = "Unwrap %i Selected Mesh"
+ else:
+ ob = "Unwrap %i Selected Meshes"
+
+ # HACK, loop until mouse is lifted.
+ '''
+ while Window.GetMouseButtons() != 0:
+ time.sleep(10)
+ '''
+
+#XXX if not Draw.PupBlock(ob % len(obList), pup_block):
+#XXX return
+#XXX del ob
+
+ # Convert from being button types
+
+ USER_PROJECTION_LIMIT_CONVERTED = cos(USER_PROJECTION_LIMIT * DEG_TO_RAD)
+ USER_PROJECTION_LIMIT_HALF_CONVERTED = cos((USER_PROJECTION_LIMIT/2) * DEG_TO_RAD)
+
+
+ # Toggle Edit mode
+ is_editmode = (context.active_object.mode == 'EDIT')
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='OBJECT')
+ # Assume face select mode! an annoying hack to toggle face select mode because Mesh dosent like faceSelectMode.
+
+ if USER_SHARE_SPACE:
+ # Sort by data name so we get consistant results
+ obList.sort(key = lambda ob: ob.data.name)
+ collected_islandList= []
+
+#XXX Window.WaitCursor(1)
+
+ time1 = time.time()
+
+ # Tag as False se we dont operate on the same mesh twice.
+#XXX bpy.data.meshes.tag = False
+ for me in bpy.data.meshes:
+ me.tag = False
+
+
+ for ob in obList:
+ me = ob.data
+
+ if me.tag or me.library:
+ continue
+
+ # Tag as used
+ me.tag = True
+
+ if not me.uv_textures: # Mesh has no UV Coords, dont bother.
+ me.uv_textures.new()
+
+ uv_layer = me.uv_textures.active.data
+ me_verts = list(me.vertices)
+
+ if USER_ONLY_SELECTED_FACES:
+ meshFaces = [thickface(f, uv_layer[i], me_verts) for i, f in enumerate(me.faces) if f.select]
+ #else:
+ # meshFaces = map(thickface, me.faces)
+
+ if not meshFaces:
+ continue
+
+#XXX Window.DrawProgressBar(0.1, 'SmartProj UV Unwrapper, mapping "%s", %i faces.' % (me.name, len(meshFaces)))
+
+ # =======
+ # Generate a projection list from face normals, this is ment to be smart :)
+
+ # make a list of face props that are in sync with meshFaces
+ # Make a Face List that is sorted by area.
+ # meshFaces = []
+
+ # meshFaces.sort( lambda a, b: cmp(b.area , a.area) ) # Biggest first.
+ meshFaces.sort( key = lambda a: -a.area )
+
+ # remove all zero area faces
+ while meshFaces and meshFaces[-1].area <= SMALL_NUM:
+ # Set their UV's to 0,0
+ for uv in meshFaces[-1].uv:
+ uv.zero()
+ meshFaces.pop()
+
+ # Smallest first is slightly more efficient, but if the user cancels early then its better we work on the larger data.
+
+ # Generate Projection Vecs
+ # 0d is 1.0
+ # 180 IS -0.59846
+
+
+ # Initialize projectVecs
+ if USER_VIEW_INIT:
+ # Generate Projection
+ projectVecs = [Vector(Window.GetViewVector()) * ob.matrix_world.inverted().to_3x3()] # We add to this allong the way
+ else:
+ projectVecs = []
+
+ newProjectVec = meshFaces[0].no
+ newProjectMeshFaces = [] # Popping stuffs it up.
+
+
+ # Predent that the most unique angke is ages away to start the loop off
+ mostUniqueAngle = -1.0
+
+ # This is popped
+ tempMeshFaces = meshFaces[:]
+
+
+
+ # This while only gathers projection vecs, faces are assigned later on.
+ while 1:
+ # If theres none there then start with the largest face
+
+ # add all the faces that are close.
+ for fIdx in range(len(tempMeshFaces)-1, -1, -1):
+ # Use half the angle limit so we dont overweight faces towards this
+ # normal and hog all the faces.
+ if newProjectVec.dot(tempMeshFaces[fIdx].no) > USER_PROJECTION_LIMIT_HALF_CONVERTED:
+ newProjectMeshFaces.append(tempMeshFaces.pop(fIdx))
+
+ # Add the average of all these faces normals as a projectionVec
+ averageVec = Vector((0.0, 0.0, 0.0))
+ if USER_AREA_WEIGHT:
+ for fprop in newProjectMeshFaces:
+ averageVec += (fprop.no * fprop.area)
+ else:
+ for fprop in newProjectMeshFaces:
+ averageVec += fprop.no
+
+ if averageVec.x != 0 or averageVec.y != 0 or averageVec.z != 0: # Avoid NAN
+ projectVecs.append(averageVec.normalized())
+
+
+ # Get the next vec!
+ # Pick the face thats most different to all existing angles :)
+ mostUniqueAngle = 1.0 # 1.0 is 0d. no difference.
+ mostUniqueIndex = 0 # dummy
+
+ for fIdx in range(len(tempMeshFaces)-1, -1, -1):
+ angleDifference = -1.0 # 180d difference.
+
+ # Get the closest vec angle we are to.
+ for p in projectVecs:
+ temp_angle_diff= p.dot(tempMeshFaces[fIdx].no)
+
+ if angleDifference < temp_angle_diff:
+ angleDifference= temp_angle_diff
+
+ if angleDifference < mostUniqueAngle:
+ # We have a new most different angle
+ mostUniqueIndex = fIdx
+ mostUniqueAngle = angleDifference
+
+ if mostUniqueAngle < USER_PROJECTION_LIMIT_CONVERTED:
+ #print 'adding', mostUniqueAngle, USER_PROJECTION_LIMIT, len(newProjectMeshFaces)
+ # Now weight the vector to all its faces, will give a more direct projection
+ # if the face its self was not representive of the normal from surrounding faces.
+
+ newProjectVec = tempMeshFaces[mostUniqueIndex].no
+ newProjectMeshFaces = [tempMeshFaces.pop(mostUniqueIndex)]
+
+
+ else:
+ if len(projectVecs) >= 1: # Must have at least 2 projections
+ break
+
+
+ # If there are only zero area faces then its possible
+ # there are no projectionVecs
+ if not len(projectVecs):
+ Draw.PupMenu('error, no projection vecs where generated, 0 area faces can cause this.')
+ return
+
+ faceProjectionGroupList =[[] for i in range(len(projectVecs)) ]
+
+ # MAP and Arrange # We know there are 3 or 4 faces here
+
+ for fIdx in range(len(meshFaces)-1, -1, -1):
+ fvec = meshFaces[fIdx].no
+ i = len(projectVecs)
+
+ # Initialize first
+ bestAng = fvec.dot(projectVecs[0])
+ bestAngIdx = 0
+
+ # Cycle through the remaining, first already done
+ while i-1:
+ i-=1
+
+ newAng = fvec.dot(projectVecs[i])
+ if newAng > bestAng: # Reverse logic for dotvecs
+ bestAng = newAng
+ bestAngIdx = i
+
+ # Store the area for later use.
+ faceProjectionGroupList[bestAngIdx].append(meshFaces[fIdx])
+
+ # Cull faceProjectionGroupList,
+
+
+ # Now faceProjectionGroupList is full of faces that face match the project Vecs list
+ for i in range(len(projectVecs)):
+ # Account for projectVecs having no faces.
+ if not faceProjectionGroupList[i]:
+ continue
+
+ # Make a projection matrix from a unit length vector.
+ MatQuat = VectoQuat(projectVecs[i])
+
+ # Get the faces UV's from the projected vertex.
+ for f in faceProjectionGroupList[i]:
+ f_uv = f.uv
+ for j, v in enumerate(f.v):
+ # XXX - note, between mathutils in 2.4 and 2.5 the order changed.
+ f_uv[j][:] = (v.co * MatQuat)[:2]
+
+
+ if USER_SHARE_SPACE:
+ # Should we collect and pack later?
+ islandList = getUvIslands(faceProjectionGroupList, me)
+ collected_islandList.extend(islandList)
+
+ else:
+ # Should we pack the islands for this 1 object?
+ islandList = getUvIslands(faceProjectionGroupList, me)
+ packIslands(islandList)
+
+
+ # update the mesh here if we need to.
+
+ # We want to pack all in 1 go, so pack now
+ if USER_SHARE_SPACE:
+#XXX Window.DrawProgressBar(0.9, "Box Packing for all objects...")
+ packIslands(collected_islandList)
+
+ print("Smart Projection time: %.2f" % (time.time() - time1))
+ # Window.DrawProgressBar(0.9, "Smart Projections done, time: %.2f sec." % (time.time() - time1))
+
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ dict_matrix.clear()
+
+#XXX Window.DrawProgressBar(1.0, "")
+#XXX Window.WaitCursor(0)
+#XXX Window.RedrawAll()
+
+"""
+ pup_block = [\
+ 'Projection',\
+* ('Angle Limit:', USER_PROJECTION_LIMIT, 1, 89, ''),\
+ ('Selected Faces Only', USER_ONLY_SELECTED_FACES, 'Use only selected faces from all selected meshes.'),\
+ ('Init from view', USER_VIEW_INIT, 'The first projection will be from the view vector.'),\
+ ('Area Weight', USER_AREA_WEIGHT, 'Weight projections vector by face area.'),\
+ '',\
+ '',\
+ '',\
+ 'UV Layout',\
+ ('Share Tex Space', USER_SHARE_SPACE, 'Objects Share texture space, map all objects into 1 uvmap.'),\
+ ('Stretch to bounds', USER_STRETCH_ASPECT, 'Stretch the final output to texture bounds.'),\
+* ('Island Margin:', USER_ISLAND_MARGIN, 0.0, 0.5, ''),\
+ 'Fill in empty areas',\
+ ('Fill Holes', USER_FILL_HOLES, 'Fill in empty areas reduced texture waistage (slow).'),\
+ ('Fill Quality:', USER_FILL_HOLES_QUALITY, 1, 100, 'Depends on fill holes, how tightly to fill UV holes, (higher is slower)'),\
+ ]
+"""
+
+from bpy.props import FloatProperty
+
+
+class SmartProject(bpy.types.Operator):
+ '''This script projection unwraps the selected faces of a mesh. it operates on all selected mesh objects, and can be used unwrap selected faces, or all faces.'''
+ bl_idname = "uv.smart_project"
+ bl_label = "Smart UV Project"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ angle_limit = FloatProperty(name="Angle Limit",
+ description="lower for more projection groups, higher for less distortion.",
+ default=66.0, min=1.0, max=89.0)
+
+ island_margin = FloatProperty(name="Island Margin",
+ description="Margin to reduce bleed from adjacent islands.",
+ default=0.0, min=0.0, max=1.0)
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object != None
+
+ def execute(self, context):
+ main(context, self.island_margin, self.angle_limit)
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self)
diff --git a/release/scripts/startup/bl_operators/vertexpaint_dirt.py b/release/scripts/startup/bl_operators/vertexpaint_dirt.py
new file mode 100644
index 00000000000..ca8a3dedd0d
--- /dev/null
+++ b/release/scripts/startup/bl_operators/vertexpaint_dirt.py
@@ -0,0 +1,176 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# Script copyright (C) Campbell J Barton
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+# --------------------------------------------------------------------------
+
+# <pep8 compliant>
+
+# History
+#
+# Originally written by Campbell Barton aka ideasman42
+#
+# 2009-11-01: * 2.5 port by Keith "Wahooney" Boshoff
+# * Replaced old method with my own, speed is similar (about 0.001 sec on Suzanne)
+# but results are far more accurate
+#
+
+
+def applyVertexDirt(me, blur_iterations, blur_strength, clamp_dirt, clamp_clean, dirt_only):
+ from mathutils import Vector
+ from math import acos
+
+ #BPyMesh.meshCalcNormals(me)
+
+ vert_tone = [0.0] * len(me.vertices)
+
+ min_tone = 180.0
+ max_tone = 0.0
+
+ # create lookup table for each vertex's connected vertices (via edges)
+ con = []
+
+ con = [[] for i in range(len(me.vertices))]
+
+ # add connected verts
+ for e in me.edges:
+ con[e.vertices[0]].append(e.vertices[1])
+ con[e.vertices[1]].append(e.vertices[0])
+
+ for i, v in enumerate(me.vertices):
+ vec = Vector()
+ no = v.normal
+ co = v.co
+
+ # get the direction of the vectors between the vertex and it's connected vertices
+ for c in con[i]:
+ vec += (me.vertices[c].co - co).normalized()
+
+ # normalize the vector by dividing by the number of connected verts
+ tot_con = len(con[i])
+
+ if tot_con == 0:
+ continue
+
+ vec /= tot_con
+
+ # angle is the acos of the dot product between vert and connected verts normals
+ ang = acos(no.dot(vec))
+
+ # enforce min/max
+ ang = max(clamp_dirt, ang)
+
+ if not dirt_only:
+ ang = min(clamp_clean, ang)
+
+ vert_tone[i] = ang
+
+ # blur tones
+ for i in range(blur_iterations):
+ # backup the original tones
+ orig_vert_tone = list(vert_tone)
+
+ # use connected verts look up for blurring
+ for j, c in enumerate(con):
+ for v in c:
+ vert_tone[j] += blur_strength * orig_vert_tone[v]
+
+ vert_tone[j] /= len(c) * blur_strength + 1
+
+ min_tone = min(vert_tone)
+ max_tone = max(vert_tone)
+
+ # debug information
+ # print(min_tone * 2 * math.pi)
+ # print(max_tone * 2 * math.pi)
+ # print(clamp_clean)
+ # print(clamp_dirt)
+
+ tone_range = max_tone - min_tone
+
+ if not tone_range:
+ return
+
+ active_col_layer = None
+
+ if len(me.vertex_colors):
+ for lay in me.vertex_colors:
+ if lay.active:
+ active_col_layer = lay.data
+ else:
+ bpy.ops.mesh.vertex_color_add()
+ me.vertex_colors[0].active = True
+ active_col_layer = me.vertex_colors[0].data
+
+ if not active_col_layer:
+ return('CANCELLED', )
+
+ for i, f in enumerate(me.faces):
+ if not me.use_paint_mask or f.select:
+
+ f_col = active_col_layer[i]
+
+ f_col = [f_col.color1, f_col.color2, f_col.color3, f_col.color4]
+
+ for j, v in enumerate(f.vertices):
+ col = f_col[j]
+ tone = vert_tone[me.vertices[v].index]
+ tone = (tone - min_tone) / tone_range
+
+ if dirt_only:
+ tone = min(tone, 0.5)
+ tone *= 2
+
+ col[0] = tone * col[0]
+ col[1] = tone * col[1]
+ col[2] = tone * col[2]
+
+
+import bpy
+from bpy.props import FloatProperty, IntProperty, BoolProperty
+
+
+class VertexPaintDirt(bpy.types.Operator):
+ bl_idname = "paint.vertex_color_dirt"
+ bl_label = "Dirty Vertex Colors"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ blur_strength = FloatProperty(name="Blur Strength", description="Blur strength per iteration", default=1.0, min=0.01, max=1.0)
+ blur_iterations = IntProperty(name="Blur Iterations", description="Number times to blur the colors. (higher blurs more)", default=1, min=0, max=40)
+ clean_angle = FloatProperty(name="Highlight Angle", description="Less then 90 limits the angle used in the tonal range", default=180.0, min=0.0, max=180.0)
+ dirt_angle = FloatProperty(name="Dirt Angle", description="Less then 90 limits the angle used in the tonal range", default=0.0, min=0.0, max=180.0)
+ dirt_only = BoolProperty(name="Dirt Only", description="Dont calculate cleans for convex areas", default=False)
+
+ def execute(self, context):
+ import time
+ from math import radians
+ obj = context.object
+
+ if not obj or obj.type != 'MESH':
+ print('Error, no active mesh object, aborting')
+ return('CANCELLED',)
+
+ mesh = obj.data
+
+ t = time.time()
+
+ applyVertexDirt(mesh, self.blur_iterations, self.blur_strength, radians(self.dirt_angle), radians(self.clean_angle), self.dirt_only)
+
+ print('Dirt calculated in %.6f' % (time.time() - t))
+
+ return {'FINISHED'}
diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py
new file mode 100644
index 00000000000..7e73fa7b647
--- /dev/null
+++ b/release/scripts/startup/bl_operators/wm.py
@@ -0,0 +1,848 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+import bpy
+from bpy.props import StringProperty, BoolProperty, IntProperty, FloatProperty
+from rna_prop_ui import rna_idprop_ui_prop_get, rna_idprop_ui_prop_clear
+
+
+class MESH_OT_delete_edgeloop(bpy.types.Operator):
+ '''Delete an edge loop by merging the faces on each side to a single face loop'''
+ bl_idname = "mesh.delete_edgeloop"
+ bl_label = "Delete Edge Loop"
+
+ def execute(self, context):
+ if 'FINISHED' in bpy.ops.transform.edge_slide(value=1.0):
+ bpy.ops.mesh.select_more()
+ bpy.ops.mesh.remove_doubles()
+ return {'FINISHED'}
+
+ return {'CANCELLED'}
+
+rna_path_prop = StringProperty(name="Context Attributes",
+ description="rna context string", maxlen=1024, default="")
+
+rna_reverse_prop = BoolProperty(name="Reverse",
+ description="Cycle backwards", default=False)
+
+rna_relative_prop = BoolProperty(name="Relative",
+ description="Apply relative to the current value (delta)",
+ default=False)
+
+
+def context_path_validate(context, data_path):
+ import sys
+ try:
+ value = eval("context.%s" % data_path) if data_path else Ellipsis
+ except AttributeError:
+ if "'NoneType'" in str(sys.exc_info()[1]):
+ # One of the items in the rna path is None, just ignore this
+ value = Ellipsis
+ else:
+ # We have a real error in the rna path, dont ignore that
+ raise
+
+ return value
+
+
+def execute_context_assign(self, context):
+ if context_path_validate(context, self.data_path) is Ellipsis:
+ return {'PASS_THROUGH'}
+
+ if getattr(self, "relative", False):
+ exec("context.%s+=self.value" % self.data_path)
+ else:
+ exec("context.%s=self.value" % self.data_path)
+
+ return {'FINISHED'}
+
+
+class BRUSH_OT_set_active_number(bpy.types.Operator):
+ '''Set active sculpt/paint brush from it's number'''
+ bl_idname = "brush.set_active_number"
+ bl_label = "Set Brush Number"
+
+ mode = StringProperty(name="mode",
+ description="Paint mode to set brush for", maxlen=1024)
+ number = IntProperty(name="number",
+ description="Brush number")
+
+ _attr_dict = {"sculpt": "use_paint_sculpt",
+ "vertex_paint": "use_paint_vertex",
+ "weight_paint": "use_paint_weight",
+ "image_paint": "use_paint_texture"}
+
+ def execute(self, context):
+ attr = self._attr_dict.get(self.mode)
+ if attr is None:
+ return {'CANCELLED'}
+
+ for i, brush in enumerate((cur for cur in bpy.data.brushes if getattr(cur, attr))):
+ if i == self.number:
+ getattr(context.tool_settings, self.mode).brush = brush
+ return {'FINISHED'}
+
+ return {'CANCELLED'}
+
+
+class WM_OT_context_set_boolean(bpy.types.Operator):
+ '''Set a context value.'''
+ bl_idname = "wm.context_set_boolean"
+ bl_label = "Context Set Boolean"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ value = BoolProperty(name="Value",
+ description="Assignment value", default=True)
+
+ execute = execute_context_assign
+
+
+class WM_OT_context_set_int(bpy.types.Operator): # same as enum
+ '''Set a context value.'''
+ bl_idname = "wm.context_set_int"
+ bl_label = "Context Set"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ value = IntProperty(name="Value", description="Assign value", default=0)
+ relative = rna_relative_prop
+
+ execute = execute_context_assign
+
+
+class WM_OT_context_scale_int(bpy.types.Operator):
+ '''Scale an int context value.'''
+ bl_idname = "wm.context_scale_int"
+ bl_label = "Context Set"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ value = FloatProperty(name="Value", description="Assign value", default=1.0)
+ always_step = BoolProperty(name="Always Step",
+ description="Always adjust the value by a minimum of 1 when 'value' is not 1.0.",
+ default=True)
+
+ def execute(self, context):
+ if context_path_validate(context, self.data_path) is Ellipsis:
+ return {'PASS_THROUGH'}
+
+ value = self.value
+ data_path = self.data_path
+
+ if value == 1.0: # nothing to do
+ return {'CANCELLED'}
+
+ if getattr(self, "always_step", False):
+ if value > 1.0:
+ add = "1"
+ func = "max"
+ else:
+ add = "-1"
+ func = "min"
+ exec("context.%s = %s(round(context.%s * value), context.%s + %s)" % (data_path, func, data_path, data_path, add))
+ else:
+ exec("context.%s *= value" % self.data_path)
+
+ return {'FINISHED'}
+
+
+class WM_OT_context_set_float(bpy.types.Operator): # same as enum
+ '''Set a context value.'''
+ bl_idname = "wm.context_set_float"
+ bl_label = "Context Set Float"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ value = FloatProperty(name="Value",
+ description="Assignment value", default=0.0)
+ relative = rna_relative_prop
+
+ execute = execute_context_assign
+
+
+class WM_OT_context_set_string(bpy.types.Operator): # same as enum
+ '''Set a context value.'''
+ bl_idname = "wm.context_set_string"
+ bl_label = "Context Set String"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ value = StringProperty(name="Value",
+ description="Assign value", maxlen=1024, default="")
+
+ execute = execute_context_assign
+
+
+class WM_OT_context_set_enum(bpy.types.Operator):
+ '''Set a context value.'''
+ bl_idname = "wm.context_set_enum"
+ bl_label = "Context Set Enum"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ value = StringProperty(name="Value",
+ description="Assignment value (as a string)",
+ maxlen=1024, default="")
+
+ execute = execute_context_assign
+
+
+class WM_OT_context_set_value(bpy.types.Operator):
+ '''Set a context value.'''
+ bl_idname = "wm.context_set_value"
+ bl_label = "Context Set Value"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ value = StringProperty(name="Value",
+ description="Assignment value (as a string)",
+ maxlen=1024, default="")
+
+ def execute(self, context):
+ if context_path_validate(context, self.data_path) is Ellipsis:
+ return {'PASS_THROUGH'}
+ exec("context.%s=%s" % (self.data_path, self.value))
+ return {'FINISHED'}
+
+
+class WM_OT_context_toggle(bpy.types.Operator):
+ '''Toggle a context value.'''
+ bl_idname = "wm.context_toggle"
+ bl_label = "Context Toggle"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+
+ def execute(self, context):
+
+ if context_path_validate(context, self.data_path) is Ellipsis:
+ return {'PASS_THROUGH'}
+
+ exec("context.%s=not (context.%s)" %
+ (self.data_path, self.data_path))
+
+ return {'FINISHED'}
+
+
+class WM_OT_context_toggle_enum(bpy.types.Operator):
+ '''Toggle a context value.'''
+ bl_idname = "wm.context_toggle_enum"
+ bl_label = "Context Toggle Values"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ value_1 = StringProperty(name="Value", \
+ description="Toggle enum", maxlen=1024, default="")
+
+ value_2 = StringProperty(name="Value", \
+ description="Toggle enum", maxlen=1024, default="")
+
+ def execute(self, context):
+
+ if context_path_validate(context, self.data_path) is Ellipsis:
+ return {'PASS_THROUGH'}
+
+ exec("context.%s = ['%s', '%s'][context.%s!='%s']" % \
+ (self.data_path, self.value_1,\
+ self.value_2, self.data_path,
+ self.value_2))
+
+ return {'FINISHED'}
+
+
+class WM_OT_context_cycle_int(bpy.types.Operator):
+ '''Set a context value. Useful for cycling active material, '''
+ '''vertex keys, groups' etc.'''
+ bl_idname = "wm.context_cycle_int"
+ bl_label = "Context Int Cycle"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ reverse = rna_reverse_prop
+
+ def execute(self, context):
+ data_path = self.data_path
+ value = context_path_validate(context, data_path)
+ if value is Ellipsis:
+ return {'PASS_THROUGH'}
+
+ if self.reverse:
+ value -= 1
+ else:
+ value += 1
+
+ exec("context.%s=value" % data_path)
+
+ if value != eval("context.%s" % data_path):
+ # relies on rna clamping int's out of the range
+ if self.reverse:
+ value = (1 << 31) - 1
+ else:
+ value = -1 << 31
+
+ exec("context.%s=value" % data_path)
+
+ return {'FINISHED'}
+
+
+class WM_OT_context_cycle_enum(bpy.types.Operator):
+ '''Toggle a context value.'''
+ bl_idname = "wm.context_cycle_enum"
+ bl_label = "Context Enum Cycle"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ reverse = rna_reverse_prop
+
+ def execute(self, context):
+
+ value = context_path_validate(context, self.data_path)
+ if value is Ellipsis:
+ return {'PASS_THROUGH'}
+
+ orig_value = value
+
+ # Have to get rna enum values
+ rna_struct_str, rna_prop_str = self.data_path.rsplit('.', 1)
+ i = rna_prop_str.find('[')
+
+ # just incse we get "context.foo.bar[0]"
+ if i != -1:
+ rna_prop_str = rna_prop_str[0:i]
+
+ rna_struct = eval("context.%s.rna_type" % rna_struct_str)
+
+ rna_prop = rna_struct.properties[rna_prop_str]
+
+ if type(rna_prop) != bpy.types.EnumProperty:
+ raise Exception("expected an enum property")
+
+ enums = rna_struct.properties[rna_prop_str].items.keys()
+ orig_index = enums.index(orig_value)
+
+ # Have the info we need, advance to the next item
+ if self.reverse:
+ if orig_index == 0:
+ advance_enum = enums[-1]
+ else:
+ advance_enum = enums[orig_index - 1]
+ else:
+ if orig_index == len(enums) - 1:
+ advance_enum = enums[0]
+ else:
+ advance_enum = enums[orig_index + 1]
+
+ # set the new value
+ exec("context.%s=advance_enum" % self.data_path)
+ return {'FINISHED'}
+
+
+class WM_OT_context_cycle_array(bpy.types.Operator):
+ '''Set a context array value.
+ Useful for cycling the active mesh edit mode.'''
+ bl_idname = "wm.context_cycle_array"
+ bl_label = "Context Array Cycle"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ reverse = rna_reverse_prop
+
+ def execute(self, context):
+ data_path = self.data_path
+ value = context_path_validate(context, data_path)
+ if value is Ellipsis:
+ return {'PASS_THROUGH'}
+
+ def cycle(array):
+ if self.reverse:
+ array.insert(0, array.pop())
+ else:
+ array.append(array.pop(0))
+ return array
+
+ exec("context.%s=cycle(context.%s[:])" % (data_path, data_path))
+
+ return {'FINISHED'}
+
+
+class WM_MT_context_menu_enum(bpy.types.Menu):
+ bl_label = ""
+ data_path = "" # BAD DESIGN, set from operator below.
+
+ def draw(self, context):
+ data_path = self.data_path
+ value = context_path_validate(bpy.context, data_path)
+ if value is Ellipsis:
+ return {'PASS_THROUGH'}
+ base_path, prop_string = data_path.rsplit(".", 1)
+ value_base = context_path_validate(context, base_path)
+
+ values = [(i.name, i.identifier) for i in value_base.bl_rna.properties[prop_string].items]
+
+ for name, identifier in values:
+ prop = self.layout.operator("wm.context_set_enum", text=name)
+ prop.data_path = data_path
+ prop.value = identifier
+
+
+class WM_OT_context_menu_enum(bpy.types.Operator):
+ bl_idname = "wm.context_menu_enum"
+ bl_label = "Context Enum Menu"
+ bl_options = {'UNDO'}
+ data_path = rna_path_prop
+
+ def execute(self, context):
+ data_path = self.data_path
+ WM_MT_context_menu_enum.data_path = data_path
+ bpy.ops.wm.call_menu(name="WM_MT_context_menu_enum")
+ return {'PASS_THROUGH'}
+
+
+class WM_OT_context_set_id(bpy.types.Operator):
+ '''Toggle a context value.'''
+ bl_idname = "wm.context_set_id"
+ bl_label = "Set Library ID"
+ bl_options = {'UNDO'}
+
+ data_path = rna_path_prop
+ value = StringProperty(name="Value",
+ description="Assign value", maxlen=1024, default="")
+
+ def execute(self, context):
+ value = self.value
+ data_path = self.data_path
+
+ # match the pointer type from the target property to bpy.data.*
+ # so we lookup the correct list.
+ data_path_base, data_path_prop = data_path.rsplit(".", 1)
+ data_prop_rna = eval("context.%s" % data_path_base).rna_type.properties[data_path_prop]
+ data_prop_rna_type = data_prop_rna.fixed_type
+
+ id_iter = None
+
+ for prop in bpy.data.rna_type.properties:
+ if prop.rna_type.identifier == "CollectionProperty":
+ if prop.fixed_type == data_prop_rna_type:
+ id_iter = prop.identifier
+ break
+
+ if id_iter:
+ value_id = getattr(bpy.data, id_iter).get(value)
+ exec("context.%s=value_id" % data_path)
+
+ return {'FINISHED'}
+
+
+doc_id = StringProperty(name="Doc ID",
+ description="", maxlen=1024, default="", options={'HIDDEN'})
+
+doc_new = StringProperty(name="Edit Description",
+ description="", maxlen=1024, default="")
+
+
+class WM_OT_context_modal_mouse(bpy.types.Operator):
+ '''Adjust arbitrary values with mouse input'''
+ bl_idname = "wm.context_modal_mouse"
+ bl_label = "Context Modal Mouse"
+
+ data_path_iter = StringProperty(description="The data path relative to the context, must point to an iterable.")
+ data_path_item = StringProperty(description="The data path from each iterable to the value (int or float)")
+ input_scale = FloatProperty(default=0.01, description="Scale the mouse movement by this value before applying the delta")
+ invert = BoolProperty(default=False, description="Invert the mouse input")
+ initial_x = IntProperty(options={'HIDDEN'})
+
+ def _values_store(self, context):
+ data_path_iter = self.data_path_iter
+ data_path_item = self.data_path_item
+
+ self._values = values = {}
+
+ for item in getattr(context, data_path_iter):
+ try:
+ value_orig = eval("item." + data_path_item)
+ except:
+ continue
+
+ # check this can be set, maybe this is library data.
+ try:
+ exec("item.%s = %s" % (data_path_item, value_orig))
+ except:
+ continue
+
+ values[item] = value_orig
+
+ def _values_delta(self, delta):
+ delta *= self.input_scale
+ if self.invert:
+ delta = - delta
+
+ data_path_item = self.data_path_item
+ for item, value_orig in self._values.items():
+ if type(value_orig) == int:
+ exec("item.%s = int(%d)" % (data_path_item, round(value_orig + delta)))
+ else:
+ exec("item.%s = %f" % (data_path_item, value_orig + delta))
+
+ def _values_restore(self):
+ data_path_item = self.data_path_item
+ for item, value_orig in self._values.items():
+ exec("item.%s = %s" % (data_path_item, value_orig))
+
+ self._values.clear()
+
+ def _values_clear(self):
+ self._values.clear()
+
+ def modal(self, context, event):
+ event_type = event.type
+
+ if event_type == 'MOUSEMOVE':
+ delta = event.mouse_x - self.initial_x
+ self._values_delta(delta)
+
+ elif 'LEFTMOUSE' == event_type:
+ self._values_clear()
+ return {'FINISHED'}
+
+ elif event_type in ('RIGHTMOUSE', 'ESC'):
+ self._values_restore()
+ return {'FINISHED'}
+
+ return {'RUNNING_MODAL'}
+
+ def invoke(self, context, event):
+ self._values_store(context)
+
+ if not self._values:
+ self.report({'WARNING'}, "Nothing to operate on: %s[ ].%s" %
+ (self.data_path_iter, self.data_path_item))
+
+ return {'CANCELLED'}
+ else:
+ self.initial_x = event.mouse_x
+
+ context.window_manager.modal_handler_add(self)
+ return {'RUNNING_MODAL'}
+
+
+class WM_OT_url_open(bpy.types.Operator):
+ "Open a website in the Webbrowser"
+ bl_idname = "wm.url_open"
+ bl_label = ""
+
+ url = StringProperty(name="URL", description="URL to open")
+
+ def execute(self, context):
+ import webbrowser
+ webbrowser.open(self.url)
+ return {'FINISHED'}
+
+
+class WM_OT_path_open(bpy.types.Operator):
+ "Open a path in a file browser"
+ bl_idname = "wm.path_open"
+ bl_label = ""
+
+ filepath = StringProperty(name="File Path", maxlen=1024, subtype='FILE_PATH')
+
+ def execute(self, context):
+ import sys
+ import os
+ import subprocess
+
+ filepath = bpy.path.abspath(self.filepath)
+ filepath = os.path.normpath(filepath)
+
+ if not os.path.exists(filepath):
+ self.report({'ERROR'}, "File '%s' not found" % filepath)
+ return {'CANCELLED'}
+
+ if sys.platform[:3] == "win":
+ subprocess.Popen(['start', filepath], shell=True)
+ elif sys.platform == 'darwin':
+ subprocess.Popen(['open', filepath])
+ else:
+ try:
+ subprocess.Popen(['xdg-open', filepath])
+ except OSError:
+ # xdg-open *should* be supported by recent Gnome, KDE, Xfce
+ pass
+
+ return {'FINISHED'}
+
+
+class WM_OT_doc_view(bpy.types.Operator):
+ '''Load online reference docs'''
+ bl_idname = "wm.doc_view"
+ bl_label = "View Documentation"
+
+ doc_id = doc_id
+ _prefix = "http://www.blender.org/documentation/blender_python_api_%s" % "_".join(str(v) for v in bpy.app.version)
+
+ def _nested_class_string(self, class_string):
+ ls = []
+ class_obj = getattr(bpy.types, class_string, None).bl_rna
+ while class_obj:
+ ls.insert(0, class_obj)
+ class_obj = class_obj.nested
+ return '.'.join(class_obj.identifier for class_obj in ls)
+
+ def execute(self, context):
+ id_split = self.doc_id.split('.')
+ if len(id_split) == 1: # rna, class
+ url = '%s/bpy.types.%s.html' % (self._prefix, id_split[0])
+ elif len(id_split) == 2: # rna, class.prop
+ class_name, class_prop = id_split
+
+ if hasattr(bpy.types, class_name.upper() + '_OT_' + class_prop):
+ url = '%s/bpy.ops.%s.html#bpy.ops.%s.%s' % \
+ (self._prefix, class_name, class_name, class_prop)
+ else:
+ # It so happens that epydoc nests these, not sphinx
+ # class_name_full = self._nested_class_string(class_name)
+ url = '%s/bpy.types.%s.html#bpy.types.%s.%s' % \
+ (self._prefix, class_name, class_name, class_prop)
+
+ else:
+ return {'PASS_THROUGH'}
+
+ import webbrowser
+ webbrowser.open(url)
+
+ return {'FINISHED'}
+
+
+class WM_OT_doc_edit(bpy.types.Operator):
+ '''Load online reference docs'''
+ bl_idname = "wm.doc_edit"
+ bl_label = "Edit Documentation"
+
+ doc_id = doc_id
+ doc_new = doc_new
+
+ _url = "http://www.mindrones.com/blender/svn/xmlrpc.php"
+
+ def _send_xmlrpc(self, data_dict):
+ print("sending data:", data_dict)
+
+ import xmlrpc.client
+ user = 'blenderuser'
+ pwd = 'blender>user'
+
+ docblog = xmlrpc.client.ServerProxy(self._url)
+ docblog.metaWeblog.newPost(1, user, pwd, data_dict, 1)
+
+ def execute(self, context):
+
+ doc_id = self.doc_id
+ doc_new = self.doc_new
+
+ class_name, class_prop = doc_id.split('.')
+
+ if not doc_new:
+ self.report({'ERROR'}, "No input given for '%s'" % doc_id)
+ return {'CANCELLED'}
+
+ # check if this is an operator
+ op_name = class_name.upper() + '_OT_' + class_prop
+ op_class = getattr(bpy.types, op_name, None)
+
+ # Upload this to the web server
+ upload = {}
+
+ if op_class:
+ rna = op_class.bl_rna
+ doc_orig = rna.description
+ if doc_orig == doc_new:
+ return {'RUNNING_MODAL'}
+
+ print("op - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
+ upload["title"] = 'OPERATOR %s:%s' % (doc_id, doc_orig)
+ else:
+ rna = getattr(bpy.types, class_name).bl_rna
+ doc_orig = rna.properties[class_prop].description
+ if doc_orig == doc_new:
+ return {'RUNNING_MODAL'}
+
+ print("rna - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
+ upload["title"] = 'RNA %s:%s' % (doc_id, doc_orig)
+
+ upload["description"] = doc_new
+
+ self._send_xmlrpc(upload)
+
+ return {'FINISHED'}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.label(text="Descriptor ID: '%s'" % self.doc_id)
+ layout.prop(self, "doc_new", text="")
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self, width=600)
+
+
+rna_path = StringProperty(name="Property Edit",
+ description="Property data_path edit", maxlen=1024, default="", options={'HIDDEN'})
+
+rna_value = StringProperty(name="Property Value",
+ description="Property value edit", maxlen=1024, default="")
+
+rna_property = StringProperty(name="Property Name",
+ description="Property name edit", maxlen=1024, default="")
+
+rna_min = FloatProperty(name="Min", default=0.0, precision=3)
+rna_max = FloatProperty(name="Max", default=1.0, precision=3)
+
+
+class WM_OT_properties_edit(bpy.types.Operator):
+ '''Internal use (edit a property data_path)'''
+ bl_idname = "wm.properties_edit"
+ bl_label = "Edit Property"
+ bl_options = {'REGISTER'} # only because invoke_props_popup requires.
+
+ data_path = rna_path
+ property = rna_property
+ value = rna_value
+ min = rna_min
+ max = rna_max
+ description = StringProperty(name="Tip", default="")
+
+ def execute(self, context):
+ data_path = self.data_path
+ value = self.value
+ prop = self.property
+ prop_old = self._last_prop[0]
+
+ try:
+ value_eval = eval(value)
+ except:
+ value_eval = value
+
+ # First remove
+ item = eval("context.%s" % data_path)
+
+ rna_idprop_ui_prop_clear(item, prop_old)
+ exec_str = "del item['%s']" % prop_old
+ # print(exec_str)
+ exec(exec_str)
+
+ # Reassign
+ exec_str = "item['%s'] = %s" % (prop, repr(value_eval))
+ # print(exec_str)
+ exec(exec_str)
+ self._last_prop[:] = [prop]
+
+ prop_type = type(item[prop])
+
+ prop_ui = rna_idprop_ui_prop_get(item, prop)
+
+ if prop_type in (float, int):
+
+ prop_ui['soft_min'] = prop_ui['min'] = prop_type(self.min)
+ prop_ui['soft_max'] = prop_ui['max'] = prop_type(self.max)
+
+ prop_ui['description'] = self.description
+
+ # otherwise existing buttons which reference freed
+ # memory may crash blender [#26510]
+ context.area.tag_redraw()
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+
+ self._last_prop = [self.property]
+
+ item = eval("context.%s" % self.data_path)
+
+ # setup defaults
+ prop_ui = rna_idprop_ui_prop_get(item, self.property, False) # dont create
+ if prop_ui:
+ self.min = prop_ui.get("min", -1000000000)
+ self.max = prop_ui.get("max", 1000000000)
+ self.description = prop_ui.get("description", "")
+
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self)
+
+
+class WM_OT_properties_add(bpy.types.Operator):
+ '''Internal use (edit a property data_path)'''
+ bl_idname = "wm.properties_add"
+ bl_label = "Add Property"
+
+ data_path = rna_path
+
+ def execute(self, context):
+ item = eval("context.%s" % self.data_path)
+
+ def unique_name(names):
+ prop = 'prop'
+ prop_new = prop
+ i = 1
+ while prop_new in names:
+ prop_new = prop + str(i)
+ i += 1
+
+ return prop_new
+
+ property = unique_name(item.keys())
+
+ item[property] = 1.0
+ return {'FINISHED'}
+
+
+class WM_OT_properties_remove(bpy.types.Operator):
+ '''Internal use (edit a property data_path)'''
+ bl_idname = "wm.properties_remove"
+ bl_label = "Remove Property"
+
+ data_path = rna_path
+ property = rna_property
+
+ def execute(self, context):
+ item = eval("context.%s" % self.data_path)
+ del item[self.property]
+ return {'FINISHED'}
+
+
+class WM_OT_keyconfig_activate(bpy.types.Operator):
+ bl_idname = "wm.keyconfig_activate"
+ bl_label = "Activate Keyconfig"
+
+ filepath = StringProperty(name="File Path", maxlen=1024)
+
+ def execute(self, context):
+ bpy.utils.keyconfig_set(self.filepath)
+ return {'FINISHED'}
+
+
+class WM_OT_sysinfo(bpy.types.Operator):
+ '''Generate System Info'''
+ bl_idname = "wm.sysinfo"
+ bl_label = "System Info"
+
+ def execute(self, context):
+ import sys_info
+ sys_info.write_sysinfo(self)
+ return {'FINISHED'}