diff options
Diffstat (limited to 'release/scripts/startup/bl_operators')
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'} |