diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2017-06-15 15:06:00 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2017-06-15 15:06:00 +0300 |
commit | c6676127556e5756e4c94f39784d27149f2eb86d (patch) | |
tree | a97b100b59224a3c3b8954b891968c4afa9bea79 /add_advanced_objects_menu | |
parent | 17d293687324e86b2e94e6ca3574e294f3da3667 (diff) |
add advanced objects: split to 2 folders menu and panel
Diffstat (limited to 'add_advanced_objects_menu')
18 files changed, 6120 insertions, 0 deletions
diff --git a/add_advanced_objects_menu/__init__.py b/add_advanced_objects_menu/__init__.py new file mode 100644 index 00000000..b1d86454 --- /dev/null +++ b/add_advanced_objects_menu/__init__.py @@ -0,0 +1,573 @@ +# ##### 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 ##### + +# Contributed to by: +# meta-androcto, Bill Currie, Jorge Hernandez - Melenedez Jacob Morris, Oscurart # +# Rebellion, Antonis Karvelas, Eleanor Howick, lijenstina, Daniel Schalla, Domlysz # +# Unnikrishnan(kodemax), Florian Meyer, Omar ahmed, Brian Hinton (Nichod), liero # +# Atom, Dannyboy, Mano-Wii, Kursad Karatas, teldredge, Phil Cote # + +bl_info = { + "name": "Add Advanced Objects", + "author": "Meta Androcto,", + "version": (0, 1, 3), + "blender": (2, 78, 0), + "location": "View3D > Add ", + "description": "Add Object & Camera extras", + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6" + "/Py/Scripts/Object/Add_Advanced", + "category": "Object"} + +if "bpy" in locals(): + import importlib + + importlib.reload(add_light_template) + importlib.reload(scene_objects_bi) + importlib.reload(scene_objects_cycles) + importlib.reload(scene_texture_render) + importlib.reload(trilighting) + importlib.reload(pixelate_3d) + importlib.reload(object_add_chain) + importlib.reload(circle_array) + importlib.reload(copy2) + importlib.reload(make_struts) + importlib.reload(random_box_structure) + importlib.reload(cubester) + importlib.reload(rope_alpha) + importlib.reload(add_mesh_aggregate) + importlib.reload(arrange_on_curve) + importlib.reload(mesh_easylattice) + + +else: + from . import add_light_template + from . import scene_objects_bi + from . import scene_objects_cycles + from . import scene_texture_render + from . import trilighting + from . import pixelate_3d + from . import object_add_chain + from . import circle_array + from . import copy2 + from . import make_struts + from . import random_box_structure + from . import cubester + from . import rope_alpha + from . import add_mesh_aggregate + from . import arrange_on_curve + from . import mesh_easylattice + + +import bpy +from bpy.types import ( + Menu, + AddonPreferences, + PropertyGroup, + ) +from bpy.props import ( + BoolProperty, + BoolVectorProperty, + EnumProperty, + FloatProperty, + FloatVectorProperty, + IntProperty, + StringProperty, + PointerProperty, + ) + + +# Define the "Scenes" menu +class INFO_MT_scene_elements_add(Menu): + bl_idname = "INFO_MT_scene_elements" + bl_label = "Test Scenes" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator("bi.add_scene", + text="Scene_Objects_BI") + layout.operator("objects_cycles.add_scene", + text="Scene_Objects_Cycles") + layout.operator("objects_texture.add_scene", + text="Scene_Textures_Cycles") + + +# Define the "Lights" menu +class INFO_MT_mesh_lamps_add(Menu): + bl_idname = "INFO_MT_scene_lamps" + bl_label = "Lighting Sets" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator("object.add_light_template", + text="Add Light Template") + layout.operator("object.trilighting", + text="Add Tri Lighting") + + +# Define the "Chains" menu +class INFO_MT_mesh_chain_add(Menu): + bl_idname = "INFO_MT_mesh_chain" + bl_label = "Chains" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator("mesh.primitive_chain_add", icon="LINKED") + layout.operator("mesh.primitive_oscurart_chain_add", icon="LINKED") + + +# Define the "Array" Menu +class INFO_MT_array_mods_add(Menu): + bl_idname = "INFO_MT_array_mods" + bl_label = "Array Mods" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + + layout.menu("INFO_MT_mesh_chain", icon="LINKED") + + layout.operator("objects.circle_array_operator", + text="Circle Array", icon="MOD_ARRAY") + layout.operator("object.agregate_mesh", + text="Aggregate Mesh", icon="MOD_ARRAY") + layout.operator("mesh.copy2", + text="Copy To Vert/Edge", icon="MOD_ARRAY") + + +# Define the "Blocks" Menu +class INFO_MT_quick_blocks_add(Menu): + bl_idname = "INFO_MT_quick_tools" + bl_label = "Block Tools" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + + layout.operator("object.pixelate", icon="MESH_GRID") + layout.operator("mesh.generate_struts", + text="Struts", icon="GRID") + layout.operator("object.make_structure", + text="Random Boxes", icon="SEQ_SEQUENCER") + layout.operator("object.easy_lattice", + text="Easy Lattice", icon="MOD_LATTICE") + + +# Define the "Phsysics Tools" Menu +class INFO_MT_Physics_tools_add(Menu): + bl_idname = "INFO_MT_physics_tools" + bl_label = "Physics Tools" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + layout.operator("object.drop_on_active", + text="Drop To Ground", icon="SORTSIZE") + layout.operator("ball.rope", + text="Wrecking Ball", icon='PHYSICS') + layout.operator("clot.rope", + text="Cloth Rope", icon='PHYSICS') + + +# Define "Extras" menu +def menu(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + self.layout.separator() + self.layout.menu("INFO_MT_scene_elements", icon="SCENE_DATA") + self.layout.menu("INFO_MT_scene_lamps", icon="LAMP_SPOT") + self.layout.separator() + self.layout.menu("INFO_MT_array_mods", icon="MOD_ARRAY") + self.layout.menu("INFO_MT_quick_tools", icon="MOD_BUILD") + self.layout.menu("INFO_MT_physics_tools", icon="PHYSICS") + + +# Addons Preferences +class AdvancedObjPreferences(AddonPreferences): + bl_idname = __name__ + + show_menu_list = BoolProperty( + name="Menu List", + description="Show/Hide the Add Menu items", + default=False + ) + show_panel_list = BoolProperty( + name="Panels List", + description="Show/Hide the Panel items", + default=False + ) + + def draw(self, context): + layout = self.layout + + icon_1 = "TRIA_RIGHT" if not self.show_menu_list else "TRIA_DOWN" + box = layout.box() + box.prop(self, "show_menu_list", emboss=False, icon=icon_1) + + if self.show_menu_list: + box.label(text="Items located in the Add Menu (default shortcut Ctrl + A):", + icon="LAYER_USED") + box.label(text="Test Scenes:", icon="LAYER_ACTIVE") + box.label(text="Scene Objects BI, Scene Objects Cycles, Scene Textures Cycles", + icon="LAYER_USED") + box.label(text="Lighting Sets:", icon="LAYER_ACTIVE") + box.label(text="Add Light Template, Add Tri Lighting", icon="LAYER_USED") + box.label(text="Array Mods:", icon="LAYER_ACTIVE") + box.label(text="Circle Array, Chains submenu, Copy Vert/Edge and Aggregate Mesh", + icon="LAYER_ACTIVE") + box.label(text="Chains Submenu - Add Chain, Chain to Bones", + icon="LAYER_ACTIVE") + box.label(text="Block Tools:", icon="LAYER_ACTIVE") + box.label(text="Pixelate Object, Struts, Random Boxes, Easy Lattice", + icon="LAYER_USED") + box.label(text="Physics Tools:", icon="LAYER_ACTIVE") + box.label(text="Drop to Ground, Wrecking Ball and Cloth Rope", icon="LAYER_USED") + + + icon_2 = "TRIA_RIGHT" if not self.show_panel_list else "TRIA_DOWN" + box = layout.box() + box.prop(self, "show_panel_list", emboss=False, icon=icon_2) + + if self.show_panel_list: + box.label(text="Panels located in 3D View Tools Region > Create", + icon="LAYER_ACTIVE") + box.label(text="CubeSter", icon="LAYER_USED") + + + +# Cubester update functions +def find_audio_length(self, context): + adv_obj = context.scene.advanced_objects + audio_file = adv_obj.cubester_audio_path + length = 0 + + if audio_file != "": + # confirm that strip hasn't been loaded yet + get_sequence = getattr(context.scene.sequence_editor, "sequences_all", []) + for strip in get_sequence: + if type(strip) == bpy.types.SoundSequence and strip.sound.filepath == audio_file: + length = strip.frame_final_duration + + if length == 0: + area = context.area + old_type = area.type + area.type = "SEQUENCE_EDITOR" + try: + bpy.ops.sequencer.sound_strip_add(filepath=audio_file) + adv_obj.cubester_check_audio = True + except Exception as e: + print("\n[Add Advanced Objects]\n Function: " + "find_audio_length\n {}\n".format(e)) + adv_obj.cubester_check_audio = False + pass + + area.type = old_type + + # find audio file + for strip in context.scene.sequence_editor.sequences_all: + if type(strip) == bpy.types.SoundSequence and strip.sound.filepath == audio_file: + adv_obj.cubester_check_audio = True + length = strip.frame_final_duration + + adv_obj.cubester_audio_file_length = length + + +# load image if possible +def adjust_selected_image(self, context): + scene = context.scene.advanced_objects + try: + image = bpy.data.images.load(scene.cubester_load_image) + scene.cubester_image = image.name + except Exception as e: + print("\n[Add Advanced Objects]\n Function: " + "adjust_selected_image\n {}\n".format(e)) + + +# load color image if possible +def adjust_selected_color_image(self, context): + scene = context.scene.advanced_objects + try: + image = bpy.data.images.load(scene.cubester_load_color_image) + scene.cubester_color_image = image.name + except Exception as e: + print("\nAdd Advanced Objects]\n Function: " + "adjust_selected_color_image\n {}\n".format(e)) + + +class AdvancedObjProperties(PropertyGroup): + # cubester + # main properties + cubester_check_audio = BoolProperty( + name="", + default=False + ) + cubester_audio_image = EnumProperty( + name="Input Type", + items=(("image", "Image", + "Use an Image as input for generating Geometry", "IMAGE_COL", 0), + ("audio", "Audio", + "Use a Sound Strip as input for generating Geometry", "FILE_SOUND", 1)) + ) + cubester_audio_file_length = IntProperty( + default=0 + ) + # audio + cubester_audio_path = StringProperty( + default="", + name="Audio File", + subtype="FILE_PATH", + update=find_audio_length + ) + cubester_audio_min_freq = IntProperty( + name="Minimum Frequency", + min=20, max=100000, + default=20 + ) + cubester_audio_max_freq = IntProperty( + name="Maximum Frequency", + min=21, max=999999, + default=5000 + ) + cubester_audio_offset_type = EnumProperty( + name="Offset Type", + items=(("freq", "Frequency Offset", ""), + ("frame", "Frame Offset", "")), + description="Type of offset per row of mesh" + ) + cubester_audio_frame_offset = IntProperty( + name="Frame Offset", + min=0, max=10, + default=2 + ) + cubester_audio_block_layout = EnumProperty( + name="Block Layout", + items=(("rectangle", "Rectangular", ""), + ("radial", "Radial", "")) + ) + cubester_audio_width_blocks = IntProperty( + name="Width Block Count", + min=1, max=10000, + default=5 + ) + cubester_audio_length_blocks = IntProperty( + name="Length Block Count", + min=1, max=10000, + default=50 + ) + # image + cubester_load_type = EnumProperty( + name="Image Input Type", + items=(("single", "Single Image", ""), + ("multiple", "Image Sequence", "")) + ) + cubester_image = StringProperty( + default="", + name="" + ) + cubester_load_image = StringProperty( + default="", + name="Load Image", + subtype="FILE_PATH", + update=adjust_selected_image + ) + cubester_skip_images = IntProperty( + name="Image Step", + min=1, max=30, + default=1, + description="Step from image to image by this number" + ) + cubester_max_images = IntProperty( + name="Max Number Of Images", + min=2, max=1000, + default=10, + description="Maximum number of images to be used" + ) + cubester_frame_step = IntProperty( + name="Frame Step Size", + min=1, max=10, + default=4, + description="The number of frames each picture is used" + ) + cubester_skip_pixels = IntProperty( + name="Skip # Pixels", + min=0, max=256, + default=64, + description="Skip this number of pixels before placing the next" + ) + cubester_mesh_style = EnumProperty( + name="Mesh Type", + items=(("blocks", "Blocks", ""), + ("plane", "Plane", "")), + description="Compose mesh of multiple blocks or of a single plane" + ) + cubester_block_style = EnumProperty( + name="Block Style", + items=(("size", "Vary Size", ""), + ("position", "Vary Position", "")), + description="Vary Z-size of block, or vary Z-position" + ) + cubester_height_scale = FloatProperty( + name="Height Scale", + subtype="DISTANCE", + min=0.1, max=2, + default=0.2 + ) + cubester_invert = BoolProperty( + name="Invert Height", + default=False + ) + # general adjustments + cubester_size_per_hundred_pixels = FloatProperty( + name="Size Per 100 Blocks/Points", + subtype="DISTANCE", + min=0.001, max=5, + default=1 + ) + # material based stuff + cubester_materials = EnumProperty( + name="Material", + items=(("vertex", "Vertex Colors", ""), + ("image", "Image", "")), + description="Color with vertex colors, or uv unwrap and use an image" + ) + cubester_use_image_color = BoolProperty( + name="Use Original Image Colors'?", + default=True, + description="Use original image colors, or replace with an another one" + ) + cubester_color_image = StringProperty( + default="", + name="" + ) + cubester_load_color_image = StringProperty( + default="", + name="Load Color Image", + subtype="FILE_PATH", + update=adjust_selected_color_image + ) + cubester_vertex_colors = {} + # advanced + cubester_advanced = BoolProperty( + name="Advanced Options", + default=False + ) + cubester_random_weights = BoolProperty( + name="Random Weights", + default=False + ) + cubester_weight_r = FloatProperty( + name="Red", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) + cubester_weight_g = FloatProperty( + name="Green", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) + cubester_weight_b = FloatProperty( + name="Blue", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) + cubester_weight_a = FloatProperty( + name="Alpha", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) + + # pixelate_3d properties + pixelate_3d_size = FloatProperty( + name="Size", + min=.05, max=5, + default=.25, + description="Size of the cube / grid" + ) + pixelate_3d_gap = IntProperty( + name="Gap", + min=0, max=90, + default=10, + subtype='PERCENTAGE', + description="Separation - percent of size" + ) + pixelate_3d_smooth = FloatProperty( + name="Smooth", + min=0, max=1, + default=.0, + description="Smooth factor when subdividing mesh" + ) + # arrange_on_curve + arrange_c_use_selected = BoolProperty( + name="Use Selected", + description="Use the selected objects to duplicate", + default=True, + ) + arrange_c_obj_arranjar = StringProperty( + name="" + ) + arrange_c_select_type = EnumProperty( + name="Type", + description="Select object or group", + items=[ + ('O', "Object", "Make duplicates of a specific object"), + ('G', "Group", "Make duplicates of the objects in a group"), + ], + default='O', + ) + + +def register(): + bpy.utils.register_module(__name__) + + bpy.types.Scene.advanced_objects = PointerProperty( + type=AdvancedObjProperties + ) + + # Add "Extras" menu to the "Add" menu + bpy.types.INFO_MT_add.append(menu) + try: + bpy.types.VIEW3D_MT_AddMenu.append(menu) + except: + pass + + +def unregister(): + # Remove "Extras" menu from the "Add" menu. + bpy.types.INFO_MT_add.remove(menu) + try: + bpy.types.VIEW3D_MT_AddMenu.remove(menu) + except: + pass + + bpy.utils.unregister_module(__name__) + del bpy.types.Scene.advanced_objects + + # cleanup Easy Lattice Scene Property if it was created + if hasattr(bpy.types.Scene, "activelatticeobject"): + del bpy.types.Scene.activelatticeobject + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/add_light_template.py b/add_advanced_objects_menu/add_light_template.py new file mode 100644 index 00000000..9e2c139f --- /dev/null +++ b/add_advanced_objects_menu/add_light_template.py @@ -0,0 +1,145 @@ +# gpl: author Rebellion + +import bpy +from bpy.types import Operator +from bpy.props import BoolProperty + + +def add_lamps(self, context): + + if self.bKeyLight: + keyLight = bpy.data.lamps.new(name="Key_Light", type="SPOT") + ob = bpy.data.objects.new("Key_Light", keyLight) + constraint = ob.constraints.new(type='COPY_LOCATION') + constraint.use_offset = True + constraint.owner_space = 'LOCAL' + constraint.target = self.camera + constraint = ob.constraints.new(type='TRACK_TO') + constraint.target = self.target + constraint.track_axis = 'TRACK_NEGATIVE_Z' + constraint.up_axis = 'UP_X' + constraint.owner_space = 'LOCAL' + bpy.context.scene.objects.link(ob) + ob.rotation_euler[2] = -0.785398 + + if self.bFillLight: + fillLight = bpy.data.lamps.new(name="Fill_Light", type="SPOT") + ob = bpy.data.objects.new("Fill_Light", fillLight) + constraint = ob.constraints.new(type='COPY_LOCATION') + constraint.use_offset = True + constraint.owner_space = 'LOCAL' + constraint.target = self.camera + constraint = ob.constraints.new(type='TRACK_TO') + constraint.target = self.target + constraint.track_axis = 'TRACK_NEGATIVE_Z' + constraint.up_axis = 'UP_X' + constraint.owner_space = 'LOCAL' + bpy.context.scene.objects.link(ob) + ob.rotation_euler[2] = 0.785398 + ob.data.energy = 0.3 + + if self.bBackLight: + backLight = bpy.data.lamps.new(name="Back_Light", type="SPOT") + ob = bpy.data.objects.new("Back_Light", backLight) + constraint = ob.constraints.new(type='COPY_LOCATION') + constraint.use_offset = True + constraint.owner_space = 'LOCAL' + constraint.target = self.camera + constraint = ob.constraints.new(type='TRACK_TO') + constraint.target = self.target + constraint.track_axis = 'TRACK_NEGATIVE_Z' + constraint.up_axis = 'UP_X' + constraint.owner_space = 'LOCAL' + bpy.context.scene.objects.link(ob) + ob.rotation_euler[2] = 3.14159 + ob.data.energy = 0.2 + + if self.camera_constraint and self.camera is not None and \ + self.camera.type == "CAMERA": + + constraint = self.camera.constraints.new(type='TRACK_TO') + constraint.target = self.target + constraint.track_axis = 'TRACK_NEGATIVE_Z' + constraint.up_axis = 'UP_Y' + + +class OBJECT_OT_add_light_template(Operator): + bl_idname = "object.add_light_template" + bl_label = "Add Light Template" + bl_description = ("Add Key, Fill and Back Lights to the Scene\n" + "Needs an existing Active Object") + bl_options = {'REGISTER', 'UNDO'} + + camera = None + target = None + + bKeyLight = BoolProperty( + name="Key Light", + description="Enable Key Light in the Scene", + default=True + ) + bFillLight = BoolProperty( + name="Fill Light", + description="Enable Fill Light in the Scene", + default=True + ) + bBackLight = BoolProperty( + name="Back Light", + description="Enable Back Light in the Scene", + default=True + ) + camera_constraint = BoolProperty( + name="Camera Constraint", + description="Add a Constraint to the Camera Object", + default=False + ) + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def execute(self, context): + try: + objects = context.selected_objects + + if len(objects) == 2: + for ob in objects: + if ob.type == 'CAMERA': + self.camera = ob + else: + self.target = ob + elif len(objects) == 1: + if objects[0].type == 'CAMERA': + self.camera = objects[0] + bpy.ops.object.empty_add() + self.target = context.active_object + else: + self.camera = context.scene.camera + self.target = context.active_object + elif len(objects) == 0: + bpy.ops.object.empty_add() + self.target = context.active_object + self.camera = context.scene.camera + + add_lamps(self, context) + + except Exception as e: + self.report({'WARNING'}, + "Some operations could not be performed (See Console for more info)") + + print("\n[Add Advanced Objects]\nOperator: " + "object.add_light_template\nError: {}".format(e)) + + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(OBJECT_OT_add_light_template) + + +def unregister(): + bpy.utils.unregister_class(OBJECT_OT_add_light_template) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/add_mesh_aggregate.py b/add_advanced_objects_menu/add_mesh_aggregate.py new file mode 100644 index 00000000..6072cb9c --- /dev/null +++ b/add_advanced_objects_menu/add_mesh_aggregate.py @@ -0,0 +1,338 @@ +# ##### 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 ##### + +# Simple aggregate of particles / meshes +# Copy the selected objects on the active object +# Based on the position of the cursor and a defined volume +# Allows to control growth by using a Build modifier + +bl_info = { + "name": "Aggregate Mesh", + "author": "liero", + "version": (0, 0, 5), + "blender": (2, 7, 0), + "location": "View3D > Tool Shelf", + "description": "Adds geometry to a mesh like in DLA aggregators", + "category": "Object"} + + +import bpy +import bmesh +from random import ( + choice, + gauss, + seed, + ) +from mathutils import Matrix +from bpy.props import ( + BoolProperty, + FloatProperty, + IntProperty, + ) +from bpy.types import Operator + + +def use_random_seed(self): + seed(self.rSeed) + return + + +def rg(n): + return (round(gauss(0, n), 2)) + + +def remover(sel=False): + bpy.ops.object.editmode_toggle() + if sel: + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.remove_doubles(threshold=0.0001) + bpy.ops.object.mode_set() + + +class OBJECT_OT_agregate_mesh(Operator): + bl_idname = "object.agregate_mesh" + bl_label = "Aggregate" + bl_description = ("Adds geometry to a mesh like in DLA aggregators\n" + "Needs at least two selected Mesh objects") + bl_options = {'REGISTER', 'UNDO', 'PRESET'} + + volX = FloatProperty( + name="Volume X", + min=0.1, max=25, + default=3, + description="The cloud around cursor" + ) + volY = FloatProperty( + name="Volume Y", + min=0.1, max=25, + default=3, + description="The cloud around cursor" + ) + volZ = FloatProperty( + name="Volume Z", + min=0.1, max=25, + default=3, + description="The cloud around cursor" + ) + baseSca = FloatProperty( + name="Scale", + min=0.01, max=5, + default=.25, + description="Particle Scale" + ) + varSca = FloatProperty( + name="Var", + min=0, max=1, + default=0, + description="Particle Scale Variation" + ) + rotX = FloatProperty( + name="Rot Var X", + min=0, max=2, + default=0, + description="X Rotation Variation" + ) + rotY = FloatProperty( + name="Rot Var Y", + min=0, max=2, + default=0, + description="Y Rotation Variation" + ) + rotZ = FloatProperty( + name="Rot Var Z", + min=0, max=2, + default=1, + description="Z Rotation Variation" + ) + rSeed = IntProperty( + name="Random seed", + min=0, max=999999, + default=1, + description="Seed to feed random values" + ) + numP = IntProperty( + name="Number", + min=1, + max=9999, soft_max=500, + default=50, + description="Number of particles" + ) + nor = BoolProperty( + name="Normal Oriented", + default=False, + description="Align Z axis with Faces normals" + ) + cent = BoolProperty( + name="Use Face Center", + default=False, + description="Center on Faces" + ) + track = BoolProperty( + name="Cursor Follows", + default=False, + description="Cursor moves as structure grows / more compact results" + ) + anim = BoolProperty( + name="Animatable", + default=False, + description="Sort faces so you can regrow with Build Modifier, materials are lost" + ) + refresh = BoolProperty( + name="Update", + default=False + ) + auto_refresh = BoolProperty( + name="Auto", + description="Auto update spline", + default=False + ) + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + row = col.row(align=True) + + if self.auto_refresh is False: + self.refresh = False + elif self.auto_refresh is True: + self.refresh = True + + row.prop(self, "auto_refresh", toggle=True, icon="AUTO") + row.prop(self, "refresh", toggle=True, icon="FILE_REFRESH") + + col = layout.column(align=True) + col.separator() + + col = layout.column(align=True) + col.prop(self, "volX", slider=True) + col.prop(self, "volY", slider=True) + col.prop(self, "volZ", slider=True) + + layout.label(text="Particles:") + col = layout.column(align=True) + col.prop(self, "baseSca", slider=True) + col.prop(self, "varSca", slider=True) + + col = layout.column(align=True) + col.prop(self, "rotX", slider=True) + col.prop(self, "rotY", slider=True) + col.prop(self, "rotZ", slider=True) + + col = layout.column(align=True) + col.prop(self, "rSeed", slider=False) + col.prop(self, "numP") + + row = layout.row(align=True) + row.prop(self, "nor") + row.prop(self, "cent") + + row = layout.row(align=True) + row.prop(self, "track") + row.prop(self, "anim") + + @classmethod + def poll(cls, context): + return (len(bpy.context.selected_objects) > 1 and + bpy.context.object.type == 'MESH') + + def invoke(self, context, event): + self.refresh = True + return self.execute(context) + + def execute(self, context): + if not self.refresh: + return {'PASS_THROUGH'} + + scn = bpy.context.scene + obj = bpy.context.active_object + + use_random_seed(self) + + mat = Matrix(( + (1, 0, 0, 0), + (0, 1, 0, 0), + (0, 0, 1, 0), + (0, 0, 0, 1)) + ) + if obj.matrix_world != mat: + self.report({'WARNING'}, + "Please, Apply transformations to Active Object first") + return{'FINISHED'} + + par = [o for o in bpy.context.selected_objects if o.type == 'MESH' and o != obj] + if not par: + return{'FINISHED'} + + bpy.ops.object.mode_set() + bpy.ops.object.select_all(action='DESELECT') + obj.select = True + msv = [] + + for i in range(len(obj.modifiers)): + msv.append(obj.modifiers[i].show_viewport) + obj.modifiers[i].show_viewport = False + + cur = scn.cursor_location + for i in range(self.numP): + + mes = choice(par).data + newobj = bpy.data.objects.new('nuevo', mes) + scn.objects.link(newobj) + origen = (rg(self.volX) + cur[0], rg(self.volY) + cur[1], rg(self.volZ) + cur[2]) + + cpom = obj.closest_point_on_mesh(origen) + + if self.cent: + bm = bmesh.new() + bm.from_mesh(obj.data) + if hasattr(bm.verts, "ensure_lookup_table"): + bm.verts.ensure_lookup_table() + bm.faces.ensure_lookup_table() + + newobj.location = bm.faces[cpom[3]].calc_center_median() + + bm.free() + else: + newobj.location = cpom[1] + + if self.nor: + newobj.rotation_mode = 'QUATERNION' + newobj.rotation_quaternion = cpom[1].to_track_quat('Z', 'Y') + newobj.rotation_mode = 'XYZ' + newobj.rotation_euler[0] += rg(self.rotX) + newobj.rotation_euler[1] += rg(self.rotY) + newobj.rotation_euler[2] += rg(self.rotZ) + else: + newobj.rotation_euler = (rg(self.rotX), rg(self.rotY), rg(self.rotZ)) + + newobj.scale = [self.baseSca + self.baseSca * rg(self.varSca)] * 3 + + if self.anim: + newobj.select = True + bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', obdata=True) + bpy.ops.object.transform_apply(location=True, rotation=True, scale=True) + + bme = bmesh.new() + bme.from_mesh(obj.data) + + tmp = bmesh.new() + tmp.from_mesh(newobj.data) + + for f in tmp.faces: + # z = len(bme.verts) + for v in f.verts: + bme.verts.new(list(v.co)) + bme.faces.new(bme.verts[-len(f.verts):]) + + bme.to_mesh(obj.data) + remover(True) + # Note: foo.user_clear() is deprecated use do_unlink=True instead + bpy.data.meshes.remove(newobj.data, do_unlink=True) + + else: + scn.objects.active = obj + newobj.select = True + bpy.ops.object.join() + + if self.track: + cur = scn.cursor_location = cpom[1] + + for i in range(len(msv)): + obj.modifiers[i].show_viewport = msv[i] + + for o in par: + o.select = True + + obj.select = True + + if self.auto_refresh is False: + self.refresh = False + + return{'FINISHED'} + + +def register(): + bpy.utils.register_class(OBJECT_OT_agregate_mesh) + + +def unregister(): + bpy.utils.unregister_class(OBJECT_OT_agregate_mesh) + + +if __name__ == '__main__': + register() diff --git a/add_advanced_objects_menu/arrange_on_curve.py b/add_advanced_objects_menu/arrange_on_curve.py new file mode 100644 index 00000000..14017480 --- /dev/null +++ b/add_advanced_objects_menu/arrange_on_curve.py @@ -0,0 +1,355 @@ +# gpl author: Mano-Wii + +bl_info = { + "name": "Arrange on Curve", + "author": "Mano-Wii", + "version": (6, 3, 0), + "blender": (2, 7, 7), + "location": "View3D > TOOLS", + "description": "Arrange objects along a curve", + "warning": "Select curve", + "wiki_url": "", + "category": "3D View" + } + +# Note: scene properties are moved into __init__ +# search for patterns advanced_objects and adv_obj + +import bpy +import mathutils +from bpy.types import ( + Operator, + Panel, + ) +from bpy.props import ( + EnumProperty, + FloatProperty, + IntProperty, + ) + +FLT_MIN = 0.004 + + +class PanelDupliCurve(Panel): + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_context = "objectmode" + bl_category = "Create" + bl_label = "Duplicate on curve" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + return context.object and context.mode == 'OBJECT' and context.object.type == 'CURVE' + + def draw(self, context): + layout = self.layout + adv_obj = context.scene.advanced_objects + + layout.prop(adv_obj, "arrange_c_use_selected") + + if not adv_obj.arrange_c_use_selected: + layout.prop(adv_obj, "arrange_c_select_type", expand=True) + if adv_obj.arrange_c_select_type == 'O': + layout.column(align=True).prop_search( + adv_obj, "arrange_c_obj_arranjar", + bpy.data, "objects" + ) + elif adv_obj.arrange_c_select_type == 'G': + layout.column(align=True).prop_search( + adv_obj, "arrange_c_obj_arranjar", + bpy.data, "groups" + ) + if context.object.type == 'CURVE': + layout.operator("object.arranjar_numa_curva", text="Arrange Objects") + + +class DupliCurve(Operator): + bl_idname = "object.arranjar_numa_curva" + bl_label = "Arrange Objects along a Curve" + bl_description = "Arange chosen / selected objects along the Active Curve" + bl_options = {'REGISTER', 'UNDO'} + + use_distance = EnumProperty( + name="Arrangement", + items=[ + ("D", "Distance", "Objects are arranged depending on the distance", 0), + ("Q", "Quantity", "Objects are arranged depending on the quantity", 1), + ("R", "Range", "Objects are arranged uniformly between the corners", 2) + ] + ) + distance = FloatProperty( + name="Distance", + description="Distance between Objects", + default=1.0, + min=FLT_MIN, + soft_min=0.1, + unit='LENGTH', + ) + object_qt = IntProperty( + name="Quantity", + description="Object amount", + default=2, + min=0, + ) + scale = FloatProperty( + name="Scale", + description="Object Scale", + default=1.0, + min=FLT_MIN, + unit='LENGTH', + ) + Yaw = FloatProperty( + name="X", + description="Rotate around the X axis (Yaw)", + default=0.0, + unit='ROTATION' + ) + Pitch = FloatProperty( + default=0.0, + description="Rotate around the Y axis (Pitch)", + name="Y", + unit='ROTATION' + ) + Roll = FloatProperty( + default=0.0, + description="Rotate around the Z axis (Roll)", + name="Z", + unit='ROTATION' + ) + max_angle = FloatProperty( + default=1.57079, + max=3.141592, + name="Angle", + unit='ROTATION' + ) + offset = FloatProperty( + default=0.0, + name="Offset", + unit='LENGTH' + ) + + @classmethod + def poll(cls, context): + return context.mode == 'OBJECT' + + def draw(self, context): + layout = self.layout + col = layout.column() + col.prop(self, "use_distance", text="") + col = layout.column(align=True) + if self.use_distance == "D": + col.prop(self, "distance") + elif self.use_distance == "Q": + col.prop(self, "object_qt") + else: + col.prop(self, "distance") + col.prop(self, "max_angle") + col.prop(self, "offset") + + col = layout.column(align=True) + col.prop(self, "scale") + col.prop(self, "Yaw") + col.prop(self, "Pitch") + col.prop(self, "Roll") + + def Glpoints(self, curve): + Gpoints = [] + for i, spline in enumerate(curve.data.splines): + segments = len(spline.bezier_points) + if segments >= 2: + r = spline.resolution_u + 1 + + points = [] + for j in range(segments): + bp1 = spline.bezier_points[j] + inext = (j + 1) + if inext == segments: + if not spline.use_cyclic_u: + break + inext = 0 + bp2 = spline.bezier_points[inext] + if bp1.handle_right_type == bp2.handle_left_type == 'VECTOR': + _points = (bp1.co, bp2.co) if j == 0 else (bp2.co,) + else: + knot1 = bp1.co + handle1 = bp1.handle_right + handle2 = bp2.handle_left + knot2 = bp2.co + _points = mathutils.geometry.interpolate_bezier(knot1, handle1, handle2, knot2, r) + points.extend(_points) + Gpoints.append(tuple((curve.matrix_world * p for p in points))) + elif len(spline.points) >= 2: + l = [curve.matrix_world * p.co.xyz for p in spline.points] + if spline.use_cyclic_u: + l.append(l[0]) + Gpoints.append(tuple(l)) + + if self.use_distance == "R": + max_angle = self.max_angle + tmp_Gpoints = [] + sp = Gpoints[i] + sp2 = [sp[0], sp[1]] + lp = sp[1] + v1 = lp - sp[0] + for p in sp[2:]: + v2 = p - lp + try: + if (3.14158 - v1.angle(v2)) < max_angle: + tmp_Gpoints.append(tuple(sp2)) + sp2 = [lp] + except Exception as e: + print("\n[Add Advanced Objects]\nOperator: " + "object.arranjar_numa_curva\nError: {}".format(e)) + pass + sp2.append(p) + v1 = v2 + lp = p + tmp_Gpoints.append(tuple(sp2)) + Gpoints = Gpoints[:i] + tmp_Gpoints + + lengths = [] + if self.use_distance != "D": + for sp in Gpoints: + lp = sp[1] + leng = (lp - sp[0]).length + for p in sp[2:]: + leng += (p - lp).length + lp = p + lengths.append(leng) + return Gpoints, lengths + + def execute(self, context): + if context.object.type != 'CURVE': + return {'CANCELLED'} + + curve = context.active_object + Gpoints, lengs = self.Glpoints(curve) + adv_obj = context.scene.advanced_objects + + if adv_obj.arrange_c_use_selected: + G_Objeto = context.selected_objects + G_Objeto.remove(curve) + + if not G_Objeto: + return {'CANCELLED'} + + elif adv_obj.arrange_c_select_type == 'O': + G_Objeto = bpy.data.objects[adv_obj.arrange_c_obj_arranjar], + elif adv_obj.arrange_c_select_type == 'G': + G_Objeto = bpy.data.groups[adv_obj.arrange_c_obj_arranjar].objects + + yawMatrix = mathutils.Matrix.Rotation(self.Yaw, 4, 'X') + pitchMatrix = mathutils.Matrix.Rotation(self.Pitch, 4, 'Y') + rollMatrix = mathutils.Matrix.Rotation(self.Roll, 4, 'Z') + + max_angle = self.max_angle # max_angle is called in Glpoints + + if self.use_distance == "D": + dist = self.distance + for sp_points in Gpoints: + dx = 0.0 # Length of initial calculation of section + last_point = sp_points[0] + j = 0 + for point in sp_points[1:]: + vetorx = point - last_point # Vector spline section + quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z') # Tracking the selected objects + quat = quat.to_matrix().to_4x4() + + v_len = vetorx.length + if v_len > 0.0: + dx += v_len # Defined length calculation equal total length of the spline section + v_norm = vetorx / v_len + while dx > dist: + object = G_Objeto[j % len(G_Objeto)] + j += 1 + dx -= dist # Calculating the remaining length of the section + obj = object.copy() + context.scene.objects.link(obj) + obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix + # Placing in the correct position + obj.matrix_world.translation = point - v_norm * dx + obj.scale *= self.scale + last_point = point + + elif self.use_distance == "Q": + object_qt = self.object_qt + 1 + for i, sp_points in enumerate(Gpoints): + dx = 0.0 # Length of initial calculation of section + dist = lengs[i] / object_qt + last_point = sp_points[0] + j = 0 + for point in sp_points[1:]: + vetorx = point - last_point # Vector spline section + # Tracking the selected objects + quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z') + quat = quat.to_matrix().to_4x4() + + v_len = vetorx.length + if v_len > 0.0: + # Defined length calculation equal total length of the spline section + dx += v_len + v_norm = vetorx / v_len + while dx > dist: + object = G_Objeto[j % len(G_Objeto)] + j += 1 + dx -= dist # Calculating the remaining length of the section + obj = object.copy() + context.scene.objects.link(obj) + obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix + # Placing in the correct position + obj.matrix_world.translation = point - v_norm * dx + obj.scale *= self.scale + last_point = point + + else: + dist = self.distance + offset2 = 2 * self.offset + for i, sp_points in enumerate(Gpoints): + leng = lengs[i] - offset2 + rest = leng % dist + offset = offset2 + rest + leng -= rest + offset /= 2 + last_point = sp_points[0] + + dx = dist - offset # Length of initial calculation of section + j = 0 + for point in sp_points[1:]: + vetorx = point - last_point # Vector spline section + # Tracking the selected objects + quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z') + quat = quat.to_matrix().to_4x4() + + v_len = vetorx.length + if v_len > 0.0: + dx += v_len + v_norm = vetorx / v_len + while dx >= dist and leng >= 0.0: + leng -= dist + dx -= dist # Calculating the remaining length of the section + object = G_Objeto[j % len(G_Objeto)] + j += 1 + obj = object.copy() + context.scene.objects.link(obj) + obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix + # Placing in the correct position + obj.matrix_world.translation = point - v_norm * dx + obj.scale *= self.scale + last_point = point + + return {"FINISHED"} + + +def register(): + bpy.utils.register_class(PanelDupliCurve) + bpy.utils.register_class(DupliCurve) + + +def unregister(): + bpy.utils.unregister_class(PanelDupliCurve) + bpy.utils.unregister_class(DupliCurve) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/circle_array.py b/add_advanced_objects_menu/circle_array.py new file mode 100644 index 00000000..af5a6a0a --- /dev/null +++ b/add_advanced_objects_menu/circle_array.py @@ -0,0 +1,166 @@ +# gpl author: Antonis Karvelas + +# -*- coding: utf-8 -*- + +bl_info = { + "name": "Circle Array", + "author": "Antonis Karvelas", + "version": (1, 0, 1), + "blender": (2, 6, 7), + "location": "View3D > Object > Circle_Array", + "description": "Uses an existing array and creates an empty, " + "rotates it properly and makes a Circle Array", + "warning": "", + "wiki_url": "", + "category": "Mesh" + } + + +import bpy +from bpy.types import Operator +from math import radians + + +class Circle_Array(Operator): + bl_label = "Circle Array" + bl_idname = "objects.circle_array_operator" + bl_description = ("Creates an Array Modifier with offset empty object\n" + "Works with Mesh, Curve, Text and Surface\n" + "Use an object with an existing Array modifier\n" + "or rotate the newly created Empty with the name pattern\n" + "EMPTY_C_Array_ if the Array doesn't exist (angle: 360/Count)") + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def check_empty_name(self, context): + new_name, def_name = "", "EMPTY_C_Array" + suffix = 1 + try: + # first slap a simple linear count + 1 for numeric suffix, if it fails + # harvest for the rightmost numbers and append the max value + list_obj = [] + obj_all = context.scene.objects + list_obj = [obj.name for obj in obj_all if obj.name.startswith(def_name)] + new_name = "{}_{}".format(def_name, len(list_obj) + 1) + + if new_name in list_obj: + from re import findall + test_num = [findall("\d+", words) for words in list_obj] + suffix += max([int(l[-1]) for l in test_num]) + new_name = "{}_{}".format(def_name, suffix) + return new_name + except: + return None + + def execute(self, context): + is_allowed = True + try: + allowed_obj = ['MESH', 'CURVE', 'SURFACE', 'FONT'] + for obj in context.selected_objects: + if obj.type not in allowed_obj: + is_allowed = False + break + + if not is_allowed: + self.report( + {"WARNING"}, + "The Active/Selected objects are not of " + "Mesh, Curve, Surface or Font type. Operation Cancelled" + ) + return {'CANCELLED'} + + default_name = self.check_empty_name(context) or "EMPTY_C_Array" + bpy.ops.object.modifier_add(type='ARRAY') + + if len(context.selected_objects) == 2: + selected = context.selected_objects + lists = [obj for obj in selected if obj != context.active_object] + active = lists[0] + # check if the list object has a modifier + check_mod = None + for mod in active.modifiers[:]: + if mod.type == "ARRAY": + check_mod = mod + break + + if check_mod: + check_mod.use_object_offset = True + check_mod.use_relative_offset = False + else: + # fallback + bpy.context.scene.objects.active = active + bpy.ops.object.modifier_add(type='ARRAY') + active.modifiers[0].use_object_offset = True + active.modifiers[0].use_relative_offset = False + + active.modifiers[0].use_object_offset = True + active.modifiers[0].use_relative_offset = False + active.select = False + bpy.context.scene.objects.active = context.active_object + bpy.ops.view3d.snap_cursor_to_selected() + + if active.modifiers[0].offset_object is None: + bpy.ops.object.add(type='EMPTY') + empty_name = bpy.context.active_object + empty_name.name = default_name + active.modifiers[0].offset_object = empty_name + else: + empty_name = active.modifiers[0].offset_object + + bpy.context.scene.objects.active = active + num = active.modifiers["Array"].count + rotate_num = 360 / num + active.select = True + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + empty_name.rotation_euler = (0, 0, radians(rotate_num)) + empty_name.select = False + active.select = True + bpy.ops.object.origin_set(type="ORIGIN_CURSOR") + + return {'FINISHED'} + else: + active = context.active_object + active.modifiers[0].use_object_offset = True + active.modifiers[0].use_relative_offset = False + bpy.ops.view3d.snap_cursor_to_selected() + + if active.modifiers[0].offset_object is None: + bpy.ops.object.add(type='EMPTY') + empty_name = bpy.context.active_object + empty_name.name = default_name + active.modifiers[0].offset_object = empty_name + else: + empty_name = active.modifiers[0].offset_object + + bpy.context.scene.objects.active = active + num = active.modifiers["Array"].count + rotate_num = 360 / num + active.select = True + bpy.ops.object.transform_apply(location=False, rotation=True, scale=True) + empty_name.rotation_euler = (0, 0, radians(rotate_num)) + empty_name.select = False + active.select = True + + return {'FINISHED'} + + except Exception as e: + self.report({'WARNING'}, + "Circle Array operator could not be executed (See the console for more info)") + print("\n[objects.circle_array_operator]\nError: {}\n".format(e)) + + return {'CANCELLED'} + + +# Register +def register(): + bpy.utils.register_class(Circle_Array) + + +def unregister(): + bpy.utils.unregister_class(Circle_Array) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/copy2.py b/add_advanced_objects_menu/copy2.py new file mode 100644 index 00000000..489f6dee --- /dev/null +++ b/add_advanced_objects_menu/copy2.py @@ -0,0 +1,339 @@ +# ##### 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 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see http://www.gnu.org/licenses/ +# or write to the Free Software Foundation, Inc., 51 Franklin Street, +# Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +bl_info = { + "name": "Copy2 Vertices, Edges or Faces", + "author": "Eleanor Howick (elfnor.com)", + "version": (0, 1, 1), + "blender": (2, 71, 0), + "location": "3D View > Object > Copy 2", + "description": "Copy one object to the selected vertices, edges or faces of another object", + "warning": "", + "category": "Object" +} + +import bpy +from bpy.types import Operator +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + ) +from mathutils import ( + Vector, + Matrix, + ) + + +class Copy2(Operator): + bl_idname = "mesh.copy2" + bl_label = "Copy 2" + bl_description = ("Copy Vertices, Edges or Faces to the Selected object\n" + "Needs an existing Active Mesh Object") + bl_options = {"REGISTER", "UNDO"} + + obj_list = None + + def obj_list_cb(self, context): + return Copy2.obj_list + + def sec_axes_list_cb(self, context): + if self.priaxes == 'X': + sec_list = [('Y', "Y", "Secondary axis Y"), + ('Z', "Z", "Secondary axis Z")] + + if self.priaxes == 'Y': + sec_list = [('X', "X", "Secondary axis X"), + ('Z', "Z", "Secondary axis Z")] + + if self.priaxes == 'Z': + sec_list = [('X', "X", "Secondary axis X"), + ('Y', "Y", "Secondary axis Y")] + return sec_list + + copytype = EnumProperty( + items=(('V', "Vertex", + "Paste the Copied Geometry to Vertices of the Active Object", 'VERTEXSEL', 0), + ('E', "Edge", + "Paste the Copied Geometry to Edges of the Active Object", 'EDGESEL', 1), + ('F', "Face", + "Paste the Copied Geometry to Faces of the Active Object", 'FACESEL', 2)), + ) + copyfromobject = EnumProperty( + name="Copy from", + description="Copy an Object from the list", + items=obj_list_cb + ) + priaxes = EnumProperty( + description="Primary axes used for Copied Object orientation", + items=(('X', "X", "Along X"), + ('Y', "Y", "Along Y"), + ('Z', "Z", "Along Z")), + ) + edgescale = BoolProperty( + name="Scale to fill edge", + default=False + ) + secaxes = EnumProperty( + name="Secondary Axis", + description="Secondary axis used for Copied Object orientation", + items=sec_axes_list_cb + ) + scale = FloatProperty( + name="Scale", + default=1.0, + min=0.0, + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return obj and obj.type == "MESH" + + def draw(self, context): + layout = self.layout + + layout.prop(self, "copyfromobject") + layout.label("to:") + layout.prop(self, "copytype", expand=True) + layout.label("Primary axis:") + layout.prop(self, "priaxes", expand=True) + layout.label("Secondary axis:") + layout.prop(self, "secaxes", expand=True) + if self.copytype == "E": + layout.prop(self, "edgescale") + if self.edgescale: + layout.prop(self, "scale") + return + + def execute(self, context): + copytoobject = context.active_object.name + axes = self.priaxes + self.secaxes + + # check if there is a problem with the strings related to some chars + copy_to_object = bpy.data.objects[copytoobject] if \ + copytoobject in bpy.data.objects else None + + copy_from_object = bpy.data.objects[self.copyfromobject] if \ + self.copyfromobject in bpy.data.objects else None + + if copy_to_object is None or copy_from_object is None: + self.report({"WARNING"}, + "There was a problem with retrieving Object data. Operation Cancelled") + return {"CANCELLED"} + try: + copy_to_from( + context.scene, + copy_to_object, + copy_from_object, + self.copytype, + axes, + self.edgescale, + self.scale + ) + except Exception as e: + self.report({"WARNING"}, + "Copy2 could not be completed (Check the Console for more info)") + print("\n[Add Advanced Objects]\nOperator: mesh.copy2\n{}\n".format(e)) + + return {"CANCELLED"} + + return {"FINISHED"} + + def invoke(self, context, event): + Copy2.obj_list = [(obj.name, obj.name, obj.name) for obj in bpy.data.objects] + + return {"FINISHED"} + + +def copy_to_from(scene, to_obj, from_obj, copymode, axes, edgescale, scale): + if copymode == 'V': + vertex_copy(scene, to_obj, from_obj, axes) + + if copymode == 'E': + # don't pass edgescalling to object types that cannot be scaled + if from_obj.type in ["CAMERA", "LAMP", "EMPTY", "ARMATURE", "SPEAKER", "META"]: + edgescale = False + edge_copy(scene, to_obj, from_obj, axes, edgescale, scale) + + if copymode == 'F': + face_copy(scene, to_obj, from_obj, axes) + + +axes_dict = {'XY': (1, 2, 0), + 'XZ': (2, 1, 0), + 'YX': (0, 2, 1), + 'YZ': (2, 0, 1), + 'ZX': (0, 1, 2), + 'ZY': (1, 0, 2)} + + +def copyto(scene, source_obj, pos, xdir, zdir, axes, scale=None): + """ + copy the source_obj to pos, so its primary axis points in zdir and its + secondary axis points in xdir + """ + copy_obj = source_obj.copy() + scene.objects.link(copy_obj) + + xdir = xdir.normalized() + zdir = zdir.normalized() + # rotation first + z_axis = zdir + x_axis = xdir + y_axis = z_axis.cross(x_axis) + # use axes_dict to assign the axis as chosen in panel + A, B, C = axes_dict[axes] + rot_mat = Matrix() + rot_mat[A].xyz = x_axis + rot_mat[B].xyz = y_axis + rot_mat[C].xyz = z_axis + rot_mat.transpose() + + # rotate object + copy_obj.matrix_world = rot_mat + + # move object into position + copy_obj.location = pos + + # scale object + if scale is not None: + copy_obj.scale = scale + + return copy_obj + + +def vertex_copy(scene, obj, source_obj, axes): + # vertex select mode + sel_verts = [] + copy_list = [] + + for v in obj.data.vertices: + if v.select is True: + sel_verts.append(v) + + # make a set for each vertex. The set contains all the connected vertices + # use sets so the list is unique + vert_con = [set() for i in range(len(obj.data.vertices))] + for e in obj.data.edges: + vert_con[e.vertices[0]].add(e.vertices[1]) + vert_con[e.vertices[1]].add(e.vertices[0]) + + for v in sel_verts: + pos = v.co * obj.matrix_world.transposed() + xco = obj.data.vertices[list(vert_con[v.index])[0]].co * obj.matrix_world.transposed() + + zdir = (v.co + v.normal) * obj.matrix_world.transposed() - pos + zdir = zdir.normalized() + + edir = pos - xco + + # edir is nor perpendicular to z dir + # want xdir to be projection of edir onto plane through pos with direction zdir + xdir = edir - edir.dot(zdir) * zdir + xdir = -xdir.normalized() + + copy = copyto(scene, source_obj, pos, xdir, zdir, axes) + copy_list.append(copy) + + # select all copied objects + for copy in copy_list: + copy.select = True + obj.select = False + + +def edge_copy(scene, obj, source_obj, axes, es, scale): + # edge select mode + sel_edges = [] + copy_list = [] + + for e in obj.data.edges: + if e.select is True: + sel_edges.append(e) + + for e in sel_edges: + # pos is average of two edge vertexs + v0 = obj.data.vertices[e.vertices[0]].co * obj.matrix_world.transposed() + v1 = obj.data.vertices[e.vertices[1]].co * obj.matrix_world.transposed() + pos = (v0 + v1) / 2 + # xdir is along edge + xdir = v0 - v1 + xlen = xdir.magnitude + xdir = xdir.normalized() + # project each edge vertex normal onto plane normal to xdir + vn0 = (obj.data.vertices[e.vertices[0]].co * obj.matrix_world.transposed() + + obj.data.vertices[e.vertices[0]].normal) - v0 + vn1 = (obj.data.vertices[e.vertices[1]].co * obj.matrix_world.transposed() + + obj.data.vertices[e.vertices[1]].normal) - v1 + vn0p = vn0 - vn0.dot(xdir) * xdir + vn1p = vn1 - vn1.dot(xdir) * xdir + # the mean of the two projected normals is the zdir + zdir = vn0p + vn1p + zdir = zdir.normalized() + escale = None + if es: + escale = Vector([1.0, 1.0, 1.0]) + i = list('XYZ').index(axes[1]) + escale[i] = scale * xlen / source_obj.dimensions[i] + + copy = copyto(scene, source_obj, pos, xdir, zdir, axes, scale=escale) + copy_list.append(copy) + + # select all copied objects + for copy in copy_list: + copy.select = True + obj.select = False + + +def face_copy(scene, obj, source_obj, axes): + # face select mode + sel_faces = [] + copy_list = [] + + for f in obj.data.polygons: + if f.select is True: + sel_faces.append(f) + + for f in sel_faces: + fco = f.center * obj.matrix_world.transposed() + # get first vertex corner of transformed object + vco = obj.data.vertices[f.vertices[0]].co * obj.matrix_world.transposed() + # get face normal of transformed object + fn = (f.center + f.normal) * obj.matrix_world.transposed() - fco + fn = fn.normalized() + + copy = copyto(scene, source_obj, fco, vco - fco, fn, axes) + copy_list.append(copy) + + # select all copied objects + for copy in copy_list: + copy.select = True + obj.select = False + + +def register(): + bpy.utils.register_class(Copy2) + + +def unregister(): + bpy.utils.unregister_class(Copy2) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/cubester.py b/add_advanced_objects_menu/cubester.py new file mode 100644 index 00000000..1a516bd0 --- /dev/null +++ b/add_advanced_objects_menu/cubester.py @@ -0,0 +1,944 @@ +# ##### 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 ##### + +# Original Author = Jacob Morris +# URL = blendingjacob.blogspot.com + +# Note: scene properties are moved into __init__ together with the 3 update functions +# for properties search for the name patterns adv_obj and advanced_objects + +bl_info = { + "name": "CubeSter", + "author": "Jacob Morris", + "version": (0, 7, 1), + "blender": (2, 78, 0), + "location": "View 3D > Toolbar > CubeSter", + "description": "Takes image, image sequence, or audio file and converts it " + "into a height map based on pixel color and alpha values", + "category": "Add Mesh" + } + +import bpy +import bmesh +from bpy.types import ( + Operator, + Panel, + ) + +import timeit +from random import uniform +from math import radians +from os import ( + path, + listdir, + ) + + +# create block at center position x, y with block width 2 * hx and 2 * hy and height of h +def create_block(x, y, hw, h, verts: list, faces: list): + if bpy.context.scene.advanced_objects.cubester_block_style == "size": + z = 0.0 + else: + z = h + h = 2 * hw + + p = len(verts) + verts += [(x - hw, y - hw, z), (x + hw, y - hw, z), (x + hw, y + hw, z), (x - hw, y + hw, z)] + verts += [(x - hw, y - hw, z + h), (x + hw, y - hw, z + h), + (x + hw, y + hw, z + h), (x - hw, y + hw, z + h)] + + faces += [(p, p + 1, p + 5, p + 4), (p + 1, p + 2, p + 6, p + 5), + (p + 2, p + 3, p + 7, p + 6), (p, p + 4, p + 7, p + 3), + (p + 4, p + 5, p + 6, p + 7), (p, p + 3, p + 2, p + 1)] + + +# go through all frames in len(frames), adjusting values at frames[x][y] +def create_f_curves(mesh, frames, frame_step_size, style): + # use data to animate mesh + action = bpy.data.actions.new("CubeSterAnimation") + + mesh.animation_data_create() + mesh.animation_data.action = action + + data_path = "vertices[%d].co" + + vert_index = 4 if style == "blocks" else 0 # index of first vertex + + # loop for every face height value + for frame_start_vert in range(len(frames[0])): + # only go once if plane, otherwise do all four vertices that are in top plane if blocks + end_point = frame_start_vert + 4 if style == "blocks" else frame_start_vert + 1 + + # loop through to get the four vertices that compose the face + for frame_vert in range(frame_start_vert, end_point): + # fcurves for x, y, z + fcurves = [action.fcurves.new(data_path % vert_index, i) for i in range(3)] + frame_counter = 0 # go through each frame and add position + temp_v = mesh.vertices[vert_index].co + + # loop through frames + for frame in frames: + # new x, y, z positions + vals = [temp_v[0], temp_v[1], frame[frame_start_vert]] + for i in range(3): # for each x, y, z set each corresponding fcurve + fcurves[i].keyframe_points.insert(frame_counter, vals[i], {'FAST'}) + + frame_counter += frame_step_size # skip frames for smoother animation + + vert_index += 1 + + # only skip vertices if made of blocks + if style == "blocks": + vert_index += 4 + + +# create material with given name, apply to object +def create_material(scene, ob, name): + mat = bpy.data.materials.new("CubeSter_" + name) + adv_obj = scene.advanced_objects + image = None + + # image + if not adv_obj.cubester_use_image_color and adv_obj.cubester_color_image in bpy.data.images: + try: + image = bpy.data.images[adv_obj.cubester_color_image] + except: + pass + else: + try: + image = bpy.data.images[adv_obj.cubester_image] + except: + pass + + if scene.render.engine == "CYCLES": + mat.use_nodes = True + nodes = mat.node_tree.nodes + + att = nodes.new("ShaderNodeAttribute") + att.attribute_name = "Col" + att.location = (-200, 300) + + att = nodes.new("ShaderNodeTexImage") + if image: + att.image = image + + if adv_obj.cubester_load_type == "multiple": + att.image.source = "SEQUENCE" + att.location = (-200, 700) + + att = nodes.new("ShaderNodeTexCoord") + att.location = (-450, 600) + + if adv_obj.cubester_materials == "image": + mat.node_tree.links.new( + nodes["Image Texture"].outputs[0], + nodes["Diffuse BSDF"].inputs[0] + ) + mat.node_tree.links.new( + nodes["Texture Coordinate"].outputs[2], + nodes["Image Texture"].inputs[0] + ) + else: + mat.node_tree.links.new( + nodes["Attribute"].outputs[0], + nodes["Diffuse BSDF"].inputs[0] + ) + else: + if adv_obj.cubester_materials == "image" or scene.render.engine != "BLENDER_RENDER": + tex = bpy.data.textures.new("CubeSter_" + name, "IMAGE") + if image: + tex.image = image + slot = mat.texture_slots.add() + slot.texture = tex + else: + mat.use_vertex_color_paint = True + + ob.data.materials.append(mat) + + +# generate mesh from audio +def create_mesh_from_audio(self, scene, verts, faces): + adv_obj = scene.advanced_objects + audio_filepath = adv_obj.cubester_audio_path + width = adv_obj.cubester_audio_width_blocks + length = adv_obj.cubester_audio_length_blocks + size_per_hundred = adv_obj.cubester_size_per_hundred_pixels + + size = size_per_hundred / 100 + + # create all blocks + y = -(width / 2) * size + (size / 2) + for r in range(width): + x = -(length / 2) * size + (size / 2) + for c in range(length): + create_block(x, y, size / 2, 1, verts, faces) + + x += size + y += size + + # create object + mesh = bpy.data.meshes.new("cubed") + mesh.from_pydata(verts, [], faces) + ob = bpy.data.objects.new("cubed", mesh) + bpy.context.scene.objects.link(ob) + bpy.context.scene.objects.active = ob + ob.select = True + + # inital vertex colors + if adv_obj.cubester_materials == "image" and adv_obj.cubester_color_image != "": + picture = bpy.data.images[adv_obj.cubester_color_image] + pixels = list(picture.pixels) + vert_colors = [] + + skip_y = int(picture.size[1] / width) + skip_x = int(picture.size[0] / length) + + for row in range(0, picture.size[1], skip_y + 1): + # go through each column, step by appropriate amount + for column in range(0, picture.size[0] * 4, 4 + skip_x * 4): + r, g, b, a = get_pixel_values(picture, pixels, row, column) + vert_colors += [(r, g, b) for i in range(24)] + + bpy.ops.mesh.vertex_color_add() + + i = 0 + vert_colors_size = len(vert_colors) + for c in ob.data.vertex_colors[0].data: + if i < vert_colors_size: + c.color = vert_colors[i] + i += 1 + + # image sequence handling + if adv_obj.cubester_load_type == "multiple": + images = find_sequence_images(self, bpy.context) + + frames_vert_colors = [] + + max_images = adv_obj.cubester_max_images + 1 if \ + len(images[0]) > adv_obj.cubester_max_images else len(images[0]) + + # goes through and for each image for each block finds new height + for image_index in range(0, max_images, adv_obj.cubester_skip_images): + filepath = images[0][image_index] + name = images[1][image_index] + picture = fetch_image(self, name, filepath) + pixels = list(picture.pixels) + + frame_colors = [] + + for row in range(0, picture.size[1], skip_y + 1): + for column in range(0, picture.size[0] * 4, 4 + skip_x * 4): + r, g, b, a = get_pixel_values(picture, pixels, row, column) + frame_colors += [(r, g, b) for i in range(24)] + + frames_vert_colors.append(frame_colors) + + adv_obj.cubester_vertex_colors[ob.name] = \ + {"type": "vertex", "frames": frames_vert_colors, + "frame_skip": adv_obj.cubester_frame_step, + "total_images": max_images} + + # either add material or create + if ("CubeSter_" + "Vertex") in bpy.data.materials: + ob.data.materials.append(bpy.data.materials["CubeSter_" + "Vertex"]) + else: + create_material(scene, ob, "Vertex") + + # set keyframe for each object as initial point + frame = [1 for i in range(int(len(verts) / 8))] + frames = [frame] + + area = bpy.context.area + old_type = area.type + area.type = "GRAPH_EDITOR" + + scene.frame_current = 0 + + create_f_curves(mesh, frames, 1, "blocks") + + # deselect all fcurves + fcurves = ob.data.animation_data.action.fcurves.data.fcurves + for i in fcurves: + i.select = False + + max_images = adv_obj.cubester_audio_max_freq + min_freq = adv_obj.cubester_audio_min_freq + freq_frame = adv_obj.cubester_audio_offset_type + + freq_step = (max_images - min_freq) / length + freq_sub_step = freq_step / width + + frame_step = adv_obj.cubester_audio_frame_offset + + # animate each block with a portion of the frequency + for c in range(length): + frame_off = 0 + for r in range(width): + if freq_frame == "frame": + scene.frame_current = frame_off + l = c * freq_step + h = (c + 1) * freq_step + frame_off += frame_step + else: + l = c * freq_step + (r * freq_sub_step) + h = c * freq_step + ((r + 1) * freq_sub_step) + + pos = c + (r * length) # block number + index = pos * 4 # first index for vertex + + # select curves + for i in range(index, index + 4): + curve = i * 3 + 2 # fcurve location + fcurves[curve].select = True + try: + bpy.ops.graph.sound_bake(filepath=bpy.path.abspath(audio_filepath), low=l, high=h) + except: + pass + + # deselect curves + for i in range(index, index + 4): + curve = i * 3 + 2 # fcurve location + fcurves[curve].select = False + + area.type = old_type + + # UV unwrap + create_uv_map(bpy.context, width, length) + + # if radial apply needed modifiers + if adv_obj.cubester_audio_block_layout == "radial": + # add bezier curve of correct width + bpy.ops.curve.primitive_bezier_circle_add() + curve = bpy.context.object + # slope determined off of collected data + curve_size = (0.319 * (width * (size * 100)) - 0.0169) / 100 + curve.dimensions = (curve_size, curve_size, 0.0) + # correct for z height + curve.scale = (curve.scale[0], curve.scale[0], curve.scale[0]) + + ob.select = True + curve.select = False + scene.objects.active = ob + + # data was collected and then multi-variable regression was done in Excel + # influence of width and length + width_infl, length_infl, intercept = -0.159125, 0.49996, 0.007637 + x_offset = ((width * (size * 100) * width_infl) + + (length * (size * 100) * length_infl) + intercept) / 100 + ob.location = (ob.location[0] + x_offset, ob.location[1], ob.location[2]) + + ob.rotation_euler = (radians(-90), 0.0, 0.0) + bpy.ops.object.modifier_add(type="CURVE") + ob.modifiers["Curve"].object = curve + ob.modifiers["Curve"].deform_axis = "POS_Z" + + +# generate mesh from image(s) +def create_mesh_from_image(self, scene, verts, faces): + context = bpy.context + adv_obj = scene.advanced_objects + picture = bpy.data.images[adv_obj.cubester_image] + pixels = list(picture.pixels) + + x_pixels = picture.size[0] / (adv_obj.cubester_skip_pixels + 1) + y_pixels = picture.size[1] / (adv_obj.cubester_skip_pixels + 1) + + width = x_pixels / 100 * adv_obj.cubester_size_per_hundred_pixels + height = y_pixels / 100 * adv_obj.cubester_size_per_hundred_pixels + + step = width / x_pixels + half_width = step / 2 + + y = -height / 2 + half_width + + vert_colors = [] + weights = [uniform(0.0, 1.0) for i in range(4)] # random weights + rows = 0 + + # go through each row of pixels stepping by adv_obj.cubester_skip_pixels + 1 + for row in range(0, picture.size[1], adv_obj.cubester_skip_pixels + 1): + rows += 1 + x = -width / 2 + half_width # reset to left edge of mesh + # go through each column, step by appropriate amount + for column in range(0, picture.size[0] * 4, 4 + adv_obj.cubester_skip_pixels * 4): + r, g, b, a = get_pixel_values(picture, pixels, row, column) + h = find_point_height(r, g, b, a, scene) + + # if not transparent + if h != -1: + if adv_obj.cubester_mesh_style == "blocks": + create_block(x, y, half_width, h, verts, faces) + vert_colors += [(r, g, b) for i in range(24)] + else: + verts += [(x, y, h)] + vert_colors += [(r, g, b) for i in range(4)] + + x += step + y += step + + # if plane not blocks, then remove last 4 items from vertex_colors + # as the faces have already wrapped around + if adv_obj.cubester_mesh_style == "plane": + del vert_colors[len(vert_colors) - 4:len(vert_colors)] + + # create faces if plane based and not block based + if adv_obj.cubester_mesh_style == "plane": + off = int(len(verts) / rows) + for r in range(rows - 1): + for c in range(off - 1): + faces += [(r * off + c, r * off + c + 1, (r + 1) * off + c + 1, (r + 1) * off + c)] + + mesh = bpy.data.meshes.new("cubed") + mesh.from_pydata(verts, [], faces) + ob = bpy.data.objects.new("cubed", mesh) + context.scene.objects.link(ob) + context.scene.objects.active = ob + ob.select = True + + # uv unwrap + if adv_obj.cubester_mesh_style == "blocks": + create_uv_map(context, rows, int(len(faces) / 6 / rows)) + else: + create_uv_map(context, rows - 1, int(len(faces) / (rows - 1))) + + # material + # determine name and if already created + if adv_obj.cubester_materials == "vertex": # vertex color + image_name = "Vertex" + elif not adv_obj.cubester_use_image_color and \ + adv_obj.cubester_color_image in bpy.data.images and \ + adv_obj.cubester_materials == "image": # replaced image + image_name = adv_obj.cubester_color_image + else: # normal image + image_name = adv_obj.cubester_image + + # either add material or create + if ("CubeSter_" + image_name) in bpy.data.materials: + ob.data.materials.append(bpy.data.materials["CubeSter_" + image_name]) + + # create material + else: + create_material(scene, ob, image_name) + + # vertex colors + bpy.ops.mesh.vertex_color_add() + i = 0 + for c in ob.data.vertex_colors[0].data: + c.color = vert_colors[i] + i += 1 + + frames = [] + # image sequence handling + if adv_obj.cubester_load_type == "multiple": + images = find_sequence_images(self, context) + frames_vert_colors = [] + + max_images = adv_obj.cubester_max_images + 1 if \ + len(images[0]) > adv_obj.cubester_max_images else len(images[0]) + + # goes through and for each image for each block finds new height + for image_index in range(0, max_images, adv_obj.cubester_skip_images): + filepath = images[0][image_index] + name = images[1][image_index] + picture = fetch_image(self, name, filepath) + pixels = list(picture.pixels) + + frame_heights = [] + frame_colors = [] + + for row in range(0, picture.size[1], adv_obj.cubester_skip_pixels + 1): + for column in range(0, picture.size[0] * 4, 4 + adv_obj.cubester_skip_pixels * 4): + r, g, b, a = get_pixel_values(picture, pixels, row, column) + h = find_point_height(r, g, b, a, scene) + + if h != -1: + frame_heights.append(h) + if adv_obj.cubester_mesh_style == "blocks": + frame_colors += [(r, g, b) for i in range(24)] + else: + frame_colors += [(r, g, b) for i in range(4)] + + if adv_obj.cubester_mesh_style == "plane": + del vert_colors[len(vert_colors) - 4:len(vert_colors)] + + frames.append(frame_heights) + frames_vert_colors.append(frame_colors) + + # determine what data to use + if adv_obj.cubester_materials == "vertex" or scene.render.engine == "BLENDER_ENGINE": + adv_obj.cubester_vertex_colors[ob.name] = { + "type": "vertex", "frames": frames_vert_colors, + "frame_skip": adv_obj.cubester_frame_step, + "total_images": max_images + } + else: + adv_obj.cubester_vertex_colors[ob.name] = { + "type": "image", "frame_skip": scene.cubester_frame_step, + "total_images": max_images + } + att = get_image_node(ob.data.materials[0]) + att.image_user.frame_duration = len(frames) * adv_obj.cubester_frame_step + + # animate mesh + create_f_curves( + mesh, frames, + adv_obj.cubester_frame_step, + adv_obj.cubester_mesh_style + ) + + +# generate uv map for object +def create_uv_map(context, rows, columns): + adv_obj = context.scene.advanced_objects + mesh = context.object.data + mesh.uv_textures.new("cubester") + bm = bmesh.new() + bm.from_mesh(mesh) + + uv_layer = bm.loops.layers.uv[0] + bm.faces.ensure_lookup_table() + + x_scale = 1 / columns + y_scale = 1 / rows + + y_pos = 0.0 + x_pos = 0.0 + count = columns - 1 # hold current count to compare to if need to go to next row + + # if blocks + if adv_obj.cubester_mesh_style == "blocks": + for fa in range(int(len(bm.faces) / 6)): + for i in range(6): + pos = (fa * 6) + i + bm.faces[pos].loops[0][uv_layer].uv = (x_pos, y_pos) + bm.faces[pos].loops[1][uv_layer].uv = (x_pos + x_scale, y_pos) + bm.faces[pos].loops[2][uv_layer].uv = (x_pos + x_scale, y_pos + y_scale) + bm.faces[pos].loops[3][uv_layer].uv = (x_pos, y_pos + y_scale) + + x_pos += x_scale + + if fa >= count: + y_pos += y_scale + x_pos = 0.0 + count += columns + + # if planes + else: + for fa in range(len(bm.faces)): + bm.faces[fa].loops[0][uv_layer].uv = (x_pos, y_pos) + bm.faces[fa].loops[1][uv_layer].uv = (x_pos + x_scale, y_pos) + bm.faces[fa].loops[2][uv_layer].uv = (x_pos + x_scale, y_pos + y_scale) + bm.faces[fa].loops[3][uv_layer].uv = (x_pos, y_pos + y_scale) + + x_pos += x_scale + + if fa >= count: + y_pos += y_scale + x_pos = 0.0 + count += columns + + bm.to_mesh(mesh) + + +# if already loaded return image, else load and return +def fetch_image(self, name, load_path): + if name in bpy.data.images: + return bpy.data.images[name] + else: + try: + image = bpy.data.images.load(load_path) + return image + except RuntimeError: + self.report({"ERROR"}, "CubeSter: '{}' could not be loaded".format(load_path)) + return None + + +# find height for point +def find_point_height(r, g, b, a, scene): + adv_obj = scene.advanced_objects + if a: # if not completely transparent + normalize = 1 + + # channel weighting + if not adv_obj.cubester_advanced: + composed = 0.25 * r + 0.25 * g + 0.25 * b + 0.25 * a + else: + # user defined weighting + if not adv_obj.cubester_random_weights: + composed = adv_obj.cubester_weight_r * r + adv_obj.cubester_weight_g * g + \ + adv_obj.cubester_weight_b * b + adv_obj.cubester_weight_a * a + total = adv_obj.cubester_weight_r + adv_obj.cubester_weight_g + adv_obj.cubester_weight_b + \ + adv_obj.cubester_weight_a + + normalize = 1 / total + # random weighting + else: + weights = [uniform(0.0, 1.0) for i in range(4)] + composed = weights[0] * r + weights[1] * g + weights[2] * b + weights[3] * a + total = weights[0] + weights[1] + weights[2] + weights[3] + normalize = 1 / total + + if adv_obj.cubester_invert: + h = (1 - composed) * adv_obj.cubester_height_scale * normalize + else: + h = composed * adv_obj.cubester_height_scale * normalize + + return h + else: + return -1 + + +# find all images that would belong to sequence +def find_sequence_images(self, context): + scene = context.scene + images = [[], []] + + if scene.advanced_objects.cubester_image in bpy.data.images: + image = bpy.data.images[scene.advanced_objects.cubester_image] + main = image.name.split(".")[0] + + # first part of name to check against other files + length = len(main) + keep_going = True + for i in range(length - 1, -1, -1): + if main[i].isdigit() and keep_going: + length -= 1 + else: + keep_going = not keep_going + name = main[0:length] + + dir_name = path.dirname(bpy.path.abspath(image.filepath)) + + try: + for file in listdir(dir_name): + if path.isfile(path.join(dir_name, file)) and file.startswith(name): + images[0].append(path.join(dir_name, file)) + images[1].append(file) + except FileNotFoundError: + self.report({"ERROR"}, "CubeSter: '{}' directory not found".format(dir_name)) + + return images + + +# get image node +def get_image_node(mat): + nodes = mat.node_tree.nodes + att = nodes["Image Texture"] + + return att + + +# get the RGBA values from pixel +def get_pixel_values(picture, pixels, row, column): + # determine i position to start at based on row and column position + i = (row * picture.size[0] * 4) + column + pixs = pixels[i: i + 4] + r = pixs[0] + g = pixs[1] + b = pixs[2] + a = pixs[3] + + return r, g, b, a + + +# frame change handler for materials +def material_frame_handler(scene): + frame = scene.frame_current + adv_obj = scene.advanced_objects + + keys = list(adv_obj.cubester_vertex_colors.keys()) + + # get keys and see if object is still in scene + for i in keys: + # if object is in scene then update information + if i in bpy.data.objects: + ob = bpy.data.objects[i] + data = adv_obj.advanced_objects.cubester_vertex_colors[ob.name] + skip_frames = data["frame_skip"] + + # update materials using vertex colors + if data['type'] == "vertex": + colors = data["frames"] + + if frame % skip_frames == 0 and 0 <= frame < (data['total_images'] - 1) * skip_frames: + use_frame = int(frame / skip_frames) + color = colors[use_frame] + + i = 0 + for c in ob.data.vertex_colors[0].data: + c.color = color[i] + i += 1 + + else: + att = get_image_node(ob.data.materials[0]) + offset = frame - int(frame / skip_frames) + att.image_user.frame_offset = -offset + + # if the object is no longer in the scene then delete then entry + else: + del adv_obj.advanced_objects.cubester_vertex_colors[i] + + +class CubeSterPanel(Panel): + bl_idname = "OBJECT_PT.cubester" + bl_label = "CubeSter" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Create" + bl_options = {"DEFAULT_CLOSED"} + bl_context = "objectmode" + + def draw(self, context): + layout = self.layout.box() + scene = bpy.context.scene + adv_obj = scene.advanced_objects + images_found = 0 + rows = 0 + columns = 0 + + layout.prop(adv_obj, "cubester_audio_image") + + if adv_obj.cubester_audio_image == "image": + box = layout.box() + box.prop(adv_obj, "cubester_load_type") + box.label("Image To Convert:") + box.prop_search(adv_obj, "cubester_image", bpy.data, "images") + box.prop(adv_obj, "cubester_load_image") + + # find number of approriate images if sequence + if adv_obj.cubester_load_type == "multiple": + box = layout.box() + # display number of images found there + images = find_sequence_images(self, context) + images_found = len(images[0]) if len(images[0]) <= adv_obj.cubester_max_images \ + else adv_obj.cubester_max_images + + if len(images[0]): + box.label(str(len(images[0])) + " Images Found", icon="PACKAGE") + + box.prop(adv_obj, "cubester_max_images") + box.prop(adv_obj, "cubester_skip_images") + box.prop(adv_obj, "cubester_frame_step") + + box = layout.box() + col = box.column(align=True) + col.prop(adv_obj, "cubester_skip_pixels") + col.prop(adv_obj, "cubester_size_per_hundred_pixels") + col.prop(adv_obj, "cubester_height_scale") + box.prop(adv_obj, "cubester_invert", icon="FILE_REFRESH") + + box = layout.box() + box.prop(adv_obj, "cubester_mesh_style", icon="MESH_GRID") + + if adv_obj.cubester_mesh_style == "blocks": + box.prop(adv_obj, "cubester_block_style") + else: + # audio file + layout.prop(adv_obj, "cubester_audio_path") + + box = layout.box() + col = box.column(align=True) + col.prop(adv_obj, "cubester_audio_min_freq") + col.prop(adv_obj, "cubester_audio_max_freq") + + box.separator() + box.prop(adv_obj, "cubester_audio_offset_type") + + if adv_obj.cubester_audio_offset_type == "frame": + box.prop(adv_obj, "cubester_audio_frame_offset") + box.prop(adv_obj, "cubester_audio_block_layout") + box.separator() + + col = box.column(align=True) + col.prop(adv_obj, "cubester_audio_width_blocks") + col.prop(adv_obj, "cubester_audio_length_blocks") + + rows = adv_obj.cubester_audio_width_blocks + columns = adv_obj.cubester_audio_length_blocks + + col.prop(adv_obj, "cubester_size_per_hundred_pixels") + + # materials + box = layout.box() + box.prop(adv_obj, "cubester_materials", icon="MATERIAL") + + if adv_obj.cubester_materials == "image": + box.prop(adv_obj, "cubester_load_type") + + # find number of approriate images if sequence + if adv_obj.cubester_load_type == "multiple": + # display number of images found there + images = find_sequence_images(self, context) + images_found = len(images[0]) if len(images[0]) <= adv_obj.cubester_max_images \ + else adv_obj.cubester_max_images + + if len(images[0]): + box.label(str(len(images[0])) + " Images Found", icon="PACKAGE") + box.prop(adv_obj, "cubester_max_images") + box.prop(adv_obj, "cubester_skip_images") + box.prop(adv_obj, "cubester_frame_step") + + box.separator() + + if adv_obj.cubester_audio_image == "image": + box.prop(adv_obj, "cubester_use_image_color", icon="COLOR") + + if not adv_obj.cubester_use_image_color or adv_obj.cubester_audio_image == "audio": + box.label("Image To Use For Colors:") + box.prop_search(adv_obj, "cubester_color_image", bpy.data, "images") + box.prop(adv_obj, "cubester_load_color_image") + + if adv_obj.cubester_image in bpy.data.images: + rows = int(bpy.data.images[adv_obj.cubester_image].size[1] / + (adv_obj.cubester_skip_pixels + 1)) + columns = int(bpy.data.images[adv_obj.cubester_image].size[0] / + (adv_obj.cubester_skip_pixels + 1)) + + box = layout.box() + + if adv_obj.cubester_mesh_style == "blocks": + box.label("Approximate Cube Count: " + str(rows * columns)) + box.label("Expected Verts/Faces: " + str(rows * columns * 8) + " / " + str(rows * columns * 6)) + else: + box.label("Approximate Point Count: " + str(rows * columns)) + box.label("Expected Verts/Faces: " + str(rows * columns) + " / " + str(rows * (columns - 1))) + + # blocks and plane generation time values + if adv_obj.cubester_mesh_style == "blocks": + slope = 0.0000876958 + intercept = 0.02501 + block_infl, frame_infl, intercept2 = 0.0025934, 0.38507, -0.5840189 + else: + slope = 0.000017753 + intercept = 0.04201 + block_infl, frame_infl, intercept2 = 0.000619, 0.344636, -0.272759 + + # if creating image based mesh + points = rows * columns + if adv_obj.cubester_audio_image == "image": + if adv_obj.cubester_load_type == "single": + time = rows * columns * slope + intercept # approximate time count for mesh + else: + time = (points * slope) + intercept + (points * block_infl) + \ + (images_found / adv_obj.cubester_skip_images * frame_infl) + intercept2 + + box.label("Images To Be Used: " + str(int(images_found / adv_obj.cubester_skip_images))) + else: + # audio based mesh + box.label("Audio Track Length: " + str(adv_obj.cubester_audio_file_length) + " frames") + + block_infl, frame_infl, intercept = 0.0948, 0.0687566, -25.85985 + time = (points * block_infl) + (adv_obj.cubester_audio_file_length * frame_infl) + intercept + if time < 0.0: # usually no audio loaded + time = 0.0 + + time_mod = "s" + if time > 60: # convert to minutes if needed + time /= 60 + time_mod = "min" + time = round(time, 3) + + box.label("Expected Time: " + str(time) + " " + time_mod) + + # advanced + if adv_obj.cubester_audio_image == "image": + icon_1 = "TRIA_DOWN" if adv_obj.cubester_advanced else "TRIA_RIGHT" + # layout.separator() + box = layout.box() + box.prop(adv_obj, "cubester_advanced", icon=icon_1) + + if adv_obj.cubester_advanced: + box.prop(adv_obj, "cubester_random_weights", icon="RNDCURVE") + + if not adv_obj.cubester_random_weights: + box.label("RGBA Channel Weights", icon="COLOR") + col = box.column(align=True) + col.prop(adv_obj, "cubester_weight_r") + col.prop(adv_obj, "cubester_weight_g") + col.prop(adv_obj, "cubester_weight_b") + col.prop(adv_obj, "cubester_weight_a") + + # generate mesh + layout.operator("mesh.cubester", icon="OBJECT_DATA") + + +class CubeSter(Operator): + bl_idname = "mesh.cubester" + bl_label = "Generate Mesh" + bl_description = "Generate a mesh from an Image or Sound File" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + verts, faces = [], [] + + start = timeit.default_timer() + scene = bpy.context.scene + adv_obj = scene.advanced_objects + + if adv_obj.cubester_audio_image == "image": + if adv_obj.cubester_image != "": + create_mesh_from_image(self, scene, verts, faces) + frames = find_sequence_images(self, context) + created = len(frames[0]) + else: + self.report({'WARNING'}, + "Please add an Image for Object generation. Operation Cancelled") + return {"CANCELLED"} + else: + if (adv_obj.cubester_audio_path != "" and + path.isfile(adv_obj.cubester_audio_path) and adv_obj.cubester_check_audio is True): + + create_mesh_from_audio(self, scene, verts, faces) + created = adv_obj.cubester_audio_file_length + else: + self.report({'WARNING'}, + "Please add an Sound File for Object generation. Operation Cancelled") + return {"CANCELLED"} + + stop = timeit.default_timer() + + if adv_obj.cubester_mesh_style == "blocks" or adv_obj.cubester_audio_image == "audio": + self.report({"INFO"}, + "CubeSter: {} blocks and {} frame(s) " + "in {}s".format(str(int(len(verts) / 8)), + str(created), + str(round(stop - start, 4))) + ) + else: + self.report({"INFO"}, + "CubeSter: {} points and {} frame(s) " + "in {}s" .format(str(len(verts)), + str(created), + str(round(stop - start, 4))) + ) + + return {"FINISHED"} + + +def register(): + bpy.utils.register_module(__name__) + bpy.app.handlers.frame_change_pre.append(material_frame_handler) + + +def unregister(): + bpy.utils.unregister_module(__name__) + bpy.app.handlers.frame_change_pre.remove(material_frame_handler) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/make_struts.py b/add_advanced_objects_menu/make_struts.py new file mode 100644 index 00000000..58e149ab --- /dev/null +++ b/add_advanced_objects_menu/make_struts.py @@ -0,0 +1,592 @@ +# Copyright (C) 2012 Bill Currie <bill@taniwha.org> +# Date: 2012/2/20 + +# ##### 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 bmesh +from bpy.types import Operator +from bpy.props import ( + FloatProperty, + IntProperty, + BoolProperty, + ) +from mathutils import ( + Vector, + Matrix, + Quaternion, + ) +from math import ( + pi, cos, + sin, + ) + +cossin = [] + +# Initialize the cossin table based on the number of segments. +# +# @param n The number of segments into which the circle will be +# divided. +# @return None + + +def build_cossin(n): + global cossin + cossin = [] + for i in range(n): + a = 2 * pi * i / n + cossin.append((cos(a), sin(a))) + + +def select_up(axis): + # if axis.length != 0 and (abs(axis[0] / axis.length) < 1e-5 and abs(axis[1] / axis.length) < 1e-5): + if (abs(axis[0] / axis.length) < 1e-5 and abs(axis[1] / axis.length) < 1e-5): + up = Vector((-1, 0, 0)) + else: + up = Vector((0, 0, 1)) + return up + +# Make a single strut in non-manifold mode. +# +# The strut will be a "cylinder" with @a n sides. The vertices of the +# cylinder will be @a od / 2 from the center of the cylinder. Optionally, +# extra loops will be placed (@a od - @a id) / 2 from either end. The +# strut will be either a simple, open-ended single-surface "cylinder", or a +# double walled "pipe" with the outer wall vertices @a od / 2 from the center +# and the inner wall vertices @a id / 2 from the center. The two walls will +# be joined together at the ends with a face ring such that the entire strut +# is a manifold object. All faces of the strut will be quads. +# +# @param v1 Vertex representing one end of the strut's center-line. +# @param v2 Vertex representing the other end of the strut's +# center-line. +# @param id The diameter of the inner wall of a solid strut. Used for +# calculating the position of the extra loops irrespective +# of the solidity of the strut. +# @param od The diameter of the outer wall of a solid strut, or the +# diameter of a non-solid strut. +# @param solid If true, the strut will be made solid such that it has an +# inner wall (diameter @a id), an outer wall (diameter +# @a od), and face rings at either end of the strut such +# the strut is a manifold object. If false, the strut is +# a simple, open-ended "cylinder". +# @param loops If true, edge loops will be placed at either end of the +# strut, (@a od - @a id) / 2 from the end of the strut. The +# loops make subsurfed solid struts work nicely. +# @return A tuple containing a list of vertices and a list of faces. +# The face vertex indices are accurate only for the list of +# vertices for the created strut. + + +def make_strut(v1, v2, ind, od, n, solid, loops): + v1 = Vector(v1) + v2 = Vector(v2) + axis = v2 - v1 + pos = [(0, od / 2)] + if loops: + pos += [((od - ind) / 2, od / 2), + (axis.length - (od - ind) / 2, od / 2)] + pos += [(axis.length, od / 2)] + if solid: + pos += [(axis.length, ind / 2)] + if loops: + pos += [(axis.length - (od - ind) / 2, ind / 2), + ((od - ind) / 2, ind / 2)] + pos += [(0, ind / 2)] + vps = len(pos) + fps = vps + if not solid: + fps -= 1 + fw = axis.copy() + fw.normalize() + up = select_up(axis) + lf = up.cross(fw) + lf.normalize() + up = fw.cross(lf) + mat = Matrix((fw, lf, up)) + mat.transpose() + verts = [None] * n * vps + faces = [None] * n * fps + for i in range(n): + base = (i - 1) * vps + x = cossin[i][0] + y = cossin[i][1] + for j in range(vps): + p = Vector((pos[j][0], pos[j][1] * x, pos[j][1] * y)) + p = mat * p + verts[i * vps + j] = p + v1 + if i: + for j in range(fps): + f = (i - 1) * fps + j + faces[f] = [base + j, base + vps + j, + base + vps + (j + 1) % vps, base + (j + 1) % vps] + base = len(verts) - vps + i = n + for j in range(fps): + f = (i - 1) * fps + j + faces[f] = [base + j, j, (j + 1) % vps, base + (j + 1) % vps] + + return verts, faces + + +# Project a point along a vector onto a plane. +# +# Really, just find the intersection of the line represented by @a point +# and @a dir with the plane represented by @a norm and @a p. However, if +# the point is on or in front of the plane, or the line is parallel to +# the plane, the original point will be returned. +# +# @param point The point to be projected onto the plane. +# @param dir The vector along which the point will be projected. +# @param norm The normal of the plane onto which the point will be +# projected. +# @param p A point through which the plane passes. +# @return A vector representing the projected point, or the +# original point. + +def project_point(point, dir, norm, p): + d = (point - p).dot(norm) + if d >= 0: + # the point is already on or in front of the plane + return point + v = dir.dot(norm) + if v * v < 1e-8: + # the plane is unreachable + return point + return point - dir * d / v + + +# Make a simple strut for debugging. +# +# The strut is just a single quad representing the Z axis of the edge. +# +# @param mesh The base mesh. Used for finding the edge vertices. +# @param edge_num The number of the current edge. For the face vertex +# indices. +# @param edge The edge for which the strut will be built. +# @param od Twice the width of the strut. +# @return A tuple containing a list of vertices and a list of faces. +# The face vertex indices are pre-adjusted by the edge +# number. +# @fixme The face vertex indices should be accurate for the local +# vertices (consistency) + +def make_debug_strut(mesh, edge_num, edge, od): + v = [mesh.verts[edge.verts[0].index].co, + mesh.verts[edge.verts[1].index].co, + None, None] + v[2] = v[1] + edge.z * od / 2 + v[3] = v[0] + edge.z * od / 2 + f = [[edge_num * 4 + 0, edge_num * 4 + 1, + edge_num * 4 + 2, edge_num * 4 + 3]] + return v, f + + +# Make a cylinder with ends clipped to the end-planes of the edge. +# +# The strut is just a single quad representing the Z axis of the edge. +# +# @param mesh The base mesh. Used for finding the edge vertices. +# @param edge_num The number of the current edge. For the face vertex +# indices. +# @param edge The edge for which the strut will be built. +# @param od The diameter of the strut. +# @return A tuple containing a list of vertices and a list of faces. +# The face vertex indices are pre-adjusted by the edge +# number. +# @fixme The face vertex indices should be accurate for the local +# vertices (consistency) + +def make_clipped_cylinder(mesh, edge_num, edge, od): + n = len(cossin) + cyl = [None] * n + v0 = mesh.verts[edge.verts[0].index].co + c0 = v0 + od * edge.y + v1 = mesh.verts[edge.verts[1].index].co + c1 = v1 - od * edge.y + for i in range(n): + x = cossin[i][0] + y = cossin[i][1] + r = (edge.z * x - edge.x * y) * od / 2 + cyl[i] = [c0 + r, c1 + r] + for p in edge.verts[0].planes: + cyl[i][0] = project_point(cyl[i][0], edge.y, p, v0) + for p in edge.verts[1].planes: + cyl[i][1] = project_point(cyl[i][1], -edge.y, p, v1) + v = [None] * n * 2 + f = [None] * n + base = edge_num * n * 2 + for i in range(n): + v[i * 2 + 0] = cyl[i][1] + v[i * 2 + 1] = cyl[i][0] + f[i] = [None] * 4 + f[i][0] = base + i * 2 + 0 + f[i][1] = base + i * 2 + 1 + f[i][2] = base + (i * 2 + 3) % (n * 2) + f[i][3] = base + (i * 2 + 2) % (n * 2) + return v, f + + +# Represent a vertex in the base mesh, with additional information. +# +# These vertices are @b not shared between edges. +# +# @var index The index of the vert in the base mesh +# @var edge The edge to which this vertex is attached. +# @var edges A tuple of indicess of edges attached to this vert, not +# including the edge to which this vertex is attached. +# @var planes List of vectors representing the normals of the planes that +# bisect the angle between this vert's edge and each other +# adjacant edge. + +class SVert: + # Create a vertex holding additional information about the bmesh vertex. + # @param bmvert The bmesh vertex for which additional information is + # to be stored. + # @param bmedge The edge to which this vertex is attached. + + def __init__(self, bmvert, bmedge, edge): + self.index = bmvert.index + self.edge = edge + edges = bmvert.link_edges[:] + edges.remove(bmedge) + self.edges = tuple(map(lambda e: e.index, edges)) + self.planes = [] + + def calc_planes(self, edges): + for ed in self.edges: + self.planes.append(calc_plane_normal(self.edge, edges[ed])) + + +# Represent an edge in the base mesh, with additional information. +# +# Edges do not share vertices so that the edge is always on the front (back? +# must verify) side of all the planes attached to its vertices. If the +# vertices were shared, the edge could be on either side of the planes, and +# there would be planes attached to the vertex that are irrelevant to the +# edge. +# +# @var index The index of the edge in the base mesh. +# @var bmedge Cached reference to this edge's bmedge +# @var verts A tuple of 2 SVert vertices, one for each end of the +# edge. The vertices are @b not shared between edges. +# However, if two edges are connected via a vertex in the +# bmesh, their corresponding SVert vertices will have the +# the same index value. +# @var x The x axis of the edges local frame of reference. +# Initially invalid. +# @var y The y axis of the edges local frame of reference. +# Initialized such that the edge runs from verts[0] to +# verts[1] along the negative y axis. +# @var z The z axis of the edges local frame of reference. +# Initially invalid. + + +class SEdge: + + def __init__(self, bmesh, bmedge): + + self.index = bmedge.index + self.bmedge = bmedge + bmesh.verts.ensure_lookup_table() + self.verts = (SVert(bmedge.verts[0], bmedge, self), + SVert(bmedge.verts[1], bmedge, self)) + self.y = (bmesh.verts[self.verts[0].index].co - + bmesh.verts[self.verts[1].index].co) + self.y.normalize() + self.x = self.z = None + + def set_frame(self, up): + self.x = self.y.cross(up) + self.x.normalize() + self.z = self.x.cross(self.y) + + def calc_frame(self, base_edge): + baxis = base_edge.y + if (self.verts[0].index == base_edge.verts[0].index or + self.verts[1].index == base_edge.verts[1].index): + axis = -self.y + elif (self.verts[0].index == base_edge.verts[1].index or + self.verts[1].index == base_edge.verts[0].index): + axis = self.y + else: + raise ValueError("edges not connected") + if baxis.dot(axis) in (-1, 1): + # aligned axis have their up/z aligned + up = base_edge.z + else: + # Get the unit vector dividing the angle (theta) between baxis and + # axis in two equal parts + h = (baxis + axis) + h.normalize() + # (cos(theta/2), sin(theta/2) * n) where n is the unit vector of the + # axis rotating baxis onto axis + q = Quaternion([baxis.dot(h)] + list(baxis.cross(h))) + # rotate the base edge's up around the rotation axis (blender + # quaternion shortcut:) + up = q * base_edge.z + self.set_frame(up) + + def calc_vert_planes(self, edges): + for v in self.verts: + v.calc_planes(edges) + + def bisect_faces(self): + n1 = self.bmedge.link_faces[0].normal + if len(self.bmedge.link_faces) > 1: + n2 = self.bmedge.link_faces[1].normal + return (n1 + n2).normalized() + return n1 + + def calc_simple_frame(self): + return self.y.cross(select_up(self.y)).normalized() + + def find_edge_frame(self, sedges): + if self.bmedge.link_faces: + return self.bisect_faces() + if self.verts[0].edges or self.verts[1].edges: + edges = list(self.verts[0].edges + self.verts[1].edges) + for i in range(len(edges)): + edges[i] = sedges[edges[i]] + while edges and edges[-1].y.cross(self.y).length < 1e-3: + edges.pop() + if not edges: + return self.calc_simple_frame() + n1 = edges[-1].y.cross(self.y).normalized() + edges.pop() + while edges and edges[-1].y.cross(self.y).cross(n1).length < 1e-3: + edges.pop() + if not edges: + return n1 + n2 = edges[-1].y.cross(self.y).normalized() + return (n1 + n2).normalized() + return self.calc_simple_frame() + + +def calc_plane_normal(edge1, edge2): + if edge1.verts[0].index == edge2.verts[0].index: + axis1 = -edge1.y + axis2 = edge2.y + elif edge1.verts[1].index == edge2.verts[1].index: + axis1 = edge1.y + axis2 = -edge2.y + elif edge1.verts[0].index == edge2.verts[1].index: + axis1 = -edge1.y + axis2 = -edge2.y + elif edge1.verts[1].index == edge2.verts[0].index: + axis1 = edge1.y + axis2 = edge2.y + else: + raise ValueError("edges not connected") + # Both axis1 and axis2 are unit vectors, so this will produce a vector + # bisects the two, so long as they are not 180 degrees apart (in which + # there are infinite solutions). + return (axis1 + axis2).normalized() + + +def build_edge_frames(edges): + edge_set = set(edges) + while edge_set: + edge_queue = [edge_set.pop()] + edge_queue[0].set_frame(edge_queue[0].find_edge_frame(edges)) + while edge_queue: + current_edge = edge_queue.pop() + for i in (0, 1): + for e in current_edge.verts[i].edges: + edge = edges[e] + if edge.x is not None: # edge already processed + continue + edge_set.remove(edge) + edge_queue.append(edge) + edge.calc_frame(current_edge) + + +def make_manifold_struts(truss_obj, od, segments): + bpy.context.scene.objects.active = truss_obj + bpy.ops.object.editmode_toggle() + truss_mesh = bmesh.from_edit_mesh(truss_obj.data).copy() + bpy.ops.object.editmode_toggle() + edges = [None] * len(truss_mesh.edges) + for i, e in enumerate(truss_mesh.edges): + edges[i] = SEdge(truss_mesh, e) + build_edge_frames(edges) + verts = [] + faces = [] + for e, edge in enumerate(edges): + # v, f = make_debug_strut(truss_mesh, e, edge, od) + edge.calc_vert_planes(edges) + v, f = make_clipped_cylinder(truss_mesh, e, edge, od) + verts += v + faces += f + return verts, faces + + +def make_simple_struts(truss_mesh, ind, od, segments, solid, loops): + vps = 2 + if solid: + vps *= 2 + if loops: + vps *= 2 + fps = vps + if not solid: + fps -= 1 + + verts = [None] * len(truss_mesh.edges) * segments * vps + faces = [None] * len(truss_mesh.edges) * segments * fps + vbase = 0 + fbase = 0 + + for e in truss_mesh.edges: + v1 = truss_mesh.vertices[e.vertices[0]] + v2 = truss_mesh.vertices[e.vertices[1]] + v, f = make_strut(v1.co, v2.co, ind, od, segments, solid, loops) + for fv in f: + for i in range(len(fv)): + fv[i] += vbase + for i in range(len(v)): + verts[vbase + i] = v[i] + for i in range(len(f)): + faces[fbase + i] = f[i] + # if not base % 12800: + # print (base * 100 / len(verts)) + vbase += vps * segments + fbase += fps * segments + + return verts, faces + + +def create_struts(self, context, ind, od, segments, solid, loops, manifold): + build_cossin(segments) + + for truss_obj in bpy.context.scene.objects: + if not truss_obj.select: + continue + truss_obj.select = False + truss_mesh = truss_obj.to_mesh(context.scene, True, 'PREVIEW') + if not truss_mesh.edges: + continue + if manifold: + verts, faces = make_manifold_struts(truss_obj, od, segments) + else: + verts, faces = make_simple_struts(truss_mesh, ind, od, segments, + solid, loops) + mesh = bpy.data.meshes.new("Struts") + mesh.from_pydata(verts, [], faces) + obj = bpy.data.objects.new("Struts", mesh) + bpy.context.scene.objects.link(obj) + obj.select = True + obj.location = truss_obj.location + bpy.context.scene.objects.active = obj + mesh.update() + + +class Struts(Operator): + bl_idname = "mesh.generate_struts" + bl_label = "Struts" + bl_description = ("Add one or more struts meshes based on selected truss meshes \n" + "Note: can get very high poly\n" + "Needs an existing Active Mesh Object") + bl_options = {'REGISTER', 'UNDO'} + + ind = FloatProperty( + name="Inside Diameter", + description="Diameter of inner surface", + min=0.0, soft_min=0.0, + max=100, soft_max=100, + default=0.04 + ) + od = FloatProperty( + name="Outside Diameter", + description="Diameter of outer surface", + min=0.001, soft_min=0.001, + max=100, soft_max=100, + default=0.05 + ) + manifold = BoolProperty( + name="Manifold", + description="Connect struts to form a single solid", + default=False + ) + solid = BoolProperty( + name="Solid", + description="Create inner surface", + default=False + ) + loops = BoolProperty( + name="Loops", + description="Create sub-surf friendly loops", + default=False + ) + segments = IntProperty( + name="Segments", + description="Number of segments around strut", + min=3, soft_min=3, + max=64, soft_max=64, + default=12 + ) + + def draw(self, context): + layout = self.layout + + col = layout.column(align=True) + col.prop(self, "ind") + col.prop(self, "od") + col.prop(self, "segments") + col.separator() + + col.prop(self, "manifold") + col.prop(self, "solid") + col.prop(self, "loops") + + @classmethod + def poll(cls, context): + obj = context.active_object + return obj is not None and obj.type == "MESH" + + def execute(self, context): + store_undo = bpy.context.user_preferences.edit.use_global_undo + bpy.context.user_preferences.edit.use_global_undo = False + keywords = self.as_keywords() + + try: + create_struts(self, context, **keywords) + bpy.context.user_preferences.edit.use_global_undo = store_undo + + return {"FINISHED"} + + except Exception as e: + bpy.context.user_preferences.edit.use_global_undo = store_undo + self.report({"WARNING"}, + "Make Struts could not be performed. Operation Cancelled") + print("\n[mesh.generate_struts]\n{}".format(e)) + return {"CANCELLED"} + + +def register(): + bpy.utils.register_module(__name__) + + +def unregister(): + bpy.utils.unregister_module(__name__) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/mesh_easylattice.py b/add_advanced_objects_menu/mesh_easylattice.py new file mode 100644 index 00000000..91a167dc --- /dev/null +++ b/add_advanced_objects_menu/mesh_easylattice.py @@ -0,0 +1,401 @@ +# ##### 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 ##### + +# TODO: find a better solution for allowing more than one lattice per scene + +bl_info = { + "name": "Easy Lattice Object", + "author": "Kursad Karatas", + "version": (0, 5, 1), + "blender": (2, 66, 0), + "location": "View3D > Easy Lattice", + "description": "Create a lattice for shape editing", + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Easy_Lattice_Editing_Addon", + "tracker_url": "https://bitbucket.org/kursad/blender_addons_easylattice/src", + "category": "Mesh"} + + +import bpy +from mathutils import ( + Matrix, + Vector, + ) +from bpy.types import Operator +from bpy.props import ( + EnumProperty, + IntProperty, + StringProperty, + ) + + +# Cleanup +def modifiersDelete(obj): + for mod in obj.modifiers: + if mod.name == "latticeeasytemp": + try: + if mod.object == bpy.data.objects['LatticeEasytTemp']: + bpy.ops.object.modifier_apply(apply_as='DATA', modifier=mod.name) + except: + bpy.ops.object.modifier_remove(modifier=mod.name) + + +def modifiersApplyRemove(obj): + bpy.ops.object.select_all(action='DESELECT') + bpy.ops.object.select_pattern(pattern=obj.name, extend=False) + bpy.context.scene.objects.active = obj + + for mod in obj.modifiers: + if mod.name == "latticeeasytemp": + if mod.object == bpy.data.objects['LatticeEasytTemp']: + bpy.ops.object.modifier_apply(apply_as='DATA', modifier=mod.name) + + +def latticeDelete(obj): + bpy.ops.object.select_all(action='DESELECT') + for ob in bpy.context.scene.objects: + if "LatticeEasytTemp" in ob.name: + ob.select = True + bpy.ops.object.delete(use_global=False) + + obj.select = True + + +def createLattice(obj, size, pos, props): + # Create lattice and object + lat = bpy.data.lattices.new('LatticeEasytTemp') + ob = bpy.data.objects.new('LatticeEasytTemp', lat) + + loc, rot, scl = getTransformations(obj) + + # the position comes from the bbox + ob.location = pos + + # the size from bbox + ob.scale = size + + # the rotation comes from the combined obj world + # matrix which was converted to euler pairs + ob.rotation_euler = buildRot_World(obj) + + ob.show_x_ray = True + # Link object to scene + scn = bpy.context.scene + scn.objects.link(ob) + scn.objects.active = ob + scn.update() + + # Set lattice attributes + lat.interpolation_type_u = props[3] + lat.interpolation_type_v = props[3] + lat.interpolation_type_w = props[3] + + lat.use_outside = False + + lat.points_u = props[0] + lat.points_v = props[1] + lat.points_w = props[2] + + return ob + + +def selectedVerts_Grp(obj): + vertices = obj.data.vertices + selverts = [] + + if obj.mode == "EDIT": + bpy.ops.object.editmode_toggle() + + for grp in obj.vertex_groups: + if "templatticegrp" in grp.name: + bpy.ops.object.vertex_group_set_active(group=grp.name) + bpy.ops.object.vertex_group_remove() + + tempgroup = obj.vertex_groups.new("templatticegrp") + + for vert in vertices: + if vert.select is True: + selverts.append(vert) + tempgroup.add([vert.index], 1.0, "REPLACE") + + return selverts + + +def getTransformations(obj): + rot = obj.rotation_euler + loc = obj.location + size = obj.scale + + return [loc, rot, size] + + +def findBBox(obj, selvertsarray): + + mat = buildTrnScl_WorldMat(obj) + mat_world = obj.matrix_world + + minx, miny, minz = selvertsarray[0].co + maxx, maxy, maxz = selvertsarray[0].co + + c = 1 + + for c in range(len(selvertsarray)): + co = selvertsarray[c].co + + if co.x < minx: + minx = co.x + if co.y < miny: + miny = co.y + if co.z < minz: + minz = co.z + + if co.x > maxx: + maxx = co.x + if co.y > maxy: + maxy = co.y + if co.z > maxz: + maxz = co.z + c += 1 + + minpoint = Vector((minx, miny, minz)) + maxpoint = Vector((maxx, maxy, maxz)) + + # middle point has to be calculated based on the real world matrix + middle = ((minpoint + maxpoint) / 2) + + minpoint = mat * minpoint # Calculate only based on loc/scale + maxpoint = mat * maxpoint # Calculate only based on loc/scale + middle = mat_world * middle # the middle has to be calculated based on the real world matrix + + size = maxpoint - minpoint + size = Vector((abs(size.x), abs(size.y), abs(size.z))) + + return [minpoint, maxpoint, size, middle] + + +def buildTrnSclMat(obj): + # This function builds a local matrix that encodes translation + # and scale and it leaves out the rotation matrix + # The rotation is applied at obejct level if there is any + mat_trans = Matrix.Translation(obj.location) + mat_scale = Matrix.Scale(obj.scale[0], 4, (1, 0, 0)) + mat_scale *= Matrix.Scale(obj.scale[1], 4, (0, 1, 0)) + mat_scale *= Matrix.Scale(obj.scale[2], 4, (0, 0, 1)) + + mat_final = mat_trans * mat_scale + + return mat_final + + +def buildTrnScl_WorldMat(obj): + # This function builds a real world matrix that encodes translation + # and scale and it leaves out the rotation matrix + # The rotation is applied at obejct level if there is any + loc, rot, scl = obj.matrix_world.decompose() + mat_trans = Matrix.Translation(loc) + + mat_scale = Matrix.Scale(scl[0], 4, (1, 0, 0)) + mat_scale *= Matrix.Scale(scl[1], 4, (0, 1, 0)) + mat_scale *= Matrix.Scale(scl[2], 4, (0, 0, 1)) + + mat_final = mat_trans * mat_scale + + return mat_final + + +# Feature use +def buildRot_WorldMat(obj): + # This function builds a real world matrix that encodes rotation + # and it leaves out translation and scale matrices + loc, rot, scl = obj.matrix_world.decompose() + rot = rot.to_euler() + + mat_rot = Matrix.Rotation(rot[0], 4, 'X') + mat_rot *= Matrix.Rotation(rot[1], 4, 'Z') + mat_rot *= Matrix.Rotation(rot[2], 4, 'Y') + return mat_rot + + +def buildTrn_WorldMat(obj): + # This function builds a real world matrix that encodes translation + # and scale and it leaves out the rotation matrix + # The rotation is applied at obejct level if there is any + loc, rot, scl = obj.matrix_world.decompose() + mat_trans = Matrix.Translation(loc) + + return mat_trans + + +def buildScl_WorldMat(obj): + # This function builds a real world matrix that encodes translation + # and scale and it leaves out the rotation matrix + # The rotation is applied at obejct level if there is any + loc, rot, scl = obj.matrix_world.decompose() + + mat_scale = Matrix.Scale(scl[0], 4, (1, 0, 0)) + mat_scale *= Matrix.Scale(scl[1], 4, (0, 1, 0)) + mat_scale *= Matrix.Scale(scl[2], 4, (0, 0, 1)) + + return mat_scale + + +def buildRot_World(obj): + # This function builds a real world rotation values + loc, rot, scl = obj.matrix_world.decompose() + rot = rot.to_euler() + + return rot + + +def run(lat_props): + obj = bpy.context.object + + if obj.type == "MESH": + # set global property for the currently active latticed object + # removed in __init__ on unregister if created + bpy.types.Scene.activelatticeobject = StringProperty( + name="currentlatticeobject", + default="" + ) + bpy.types.Scene.activelatticeobject = obj.name + + modifiersDelete(obj) + selvertsarray = selectedVerts_Grp(obj) + bbox = findBBox(obj, selvertsarray) + + size = bbox[2] + pos = bbox[3] + + latticeDelete(obj) + lat = createLattice(obj, size, pos, lat_props) + + modif = obj.modifiers.new("latticeeasytemp", "LATTICE") + modif.object = lat + modif.vertex_group = "templatticegrp" + + bpy.ops.object.select_all(action='DESELECT') + bpy.ops.object.select_pattern(pattern=lat.name, extend=False) + bpy.context.scene.objects.active = lat + + bpy.context.scene.update() + bpy.ops.object.mode_set(mode='EDIT') + + if obj.type == "LATTICE": + if bpy.types.Scene.activelatticeobject: + name = bpy.types.Scene.activelatticeobject + + # Are we in edit lattice mode? If so move on to object mode + if obj.mode == "EDIT": + bpy.ops.object.editmode_toggle() + + for ob in bpy.context.scene.objects: + if ob.name == name: # found the object with the lattice mod + object = ob + modifiersApplyRemove(object) + latticeDelete(obj) + + return + + +def main(context, latticeprops): + run(latticeprops) + + +class EasyLattice(Operator): + bl_idname = "object.easy_lattice" + bl_label = "Easy Lattice Creator" + bl_description = ("Create a Lattice modifier ready to edit\n" + "Needs an existing Active Mesh Object\n" + "Note: Works only with one lattice per scene") + + lat_u = IntProperty( + name="Lattice u", + description="Points in u direction", + default=3 + ) + lat_w = IntProperty( + name="Lattice w", + description="Points in w direction", + default=3 + ) + lat_m = IntProperty( + name="Lattice m", + description="Points in m direction", + default=3 + ) + lat_types = (('KEY_LINEAR', "Linear", "Linear Interpolation type"), + ('KEY_CARDINAL', "Cardinal", "Cardinal Interpolation type"), + ('KEY_BSPLINE', "BSpline", "Key BSpline Interpolation Type") + ) + lat_type = EnumProperty( + name="Lattice Type", + description="Choose Lattice Type", + items=lat_types, + default='KEY_LINEAR' + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return obj is not None and obj.type == "MESH" + + def draw(self, context): + layout = self.layout + + col = layout.column(align=True) + col.prop(self, "lat_u") + col.prop(self, "lat_w") + col.prop(self, "lat_m") + + layout.prop(self, "lat_type") + + def execute(self, context): + lat_u = self.lat_u + lat_w = self.lat_w + lat_m = self.lat_m + + # enum property no need to complicate things + lat_type = self.lat_type + lat_props = [lat_u, lat_w, lat_m, lat_type] + try: + main(context, lat_props) + + except Exception as e: + print("\n[Add Advanced Objects]\nOperator:object.easy_lattice\n{}\n".format(e)) + self.report({'WARNING'}, + "Easy Lattice Creator could not be completed (See Console for more info)") + + return {"CANCELLED"} + + return {"FINISHED"} + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) + + +def register(): + bpy.utils.register_class(EasyLattice) + + +def unregister(): + bpy.utils.unregister_class(EasyLattice) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/object_add_chain.py b/add_advanced_objects_menu/object_add_chain.py new file mode 100644 index 00000000..8b182c82 --- /dev/null +++ b/add_advanced_objects_menu/object_add_chain.py @@ -0,0 +1,179 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +bl_info = { + "name": "Add Chain", + "author": "Brian Hinton (Nichod)", + "version": (0, 1, 2), + "blender": (2, 71, 0), + "location": "Toolshelf > Create Tab", + "description": "Adds Chain with curve guide for easy creation", + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" + "Scripts/Object/Add_Chain", + "category": "Object", +} + +import bpy +from bpy.types import Operator + + +def Add_Chain(): + # Adds Empty to scene + bpy.ops.object.add( + type='EMPTY', + view_align=False, + enter_editmode=False, + location=(0, 0, 0), + rotation=(0, 0, 0), + ) + + # Changes name of Empty to rot_link adds variable emp + emp = bpy.context.object + emp.name = "rot_link" + + # Rotate emp ~ 90 degrees + emp.rotation_euler = [1.570796, 0, 0] + + # Adds Curve Path to scene + bpy.ops.curve.primitive_nurbs_path_add( + view_align=False, + enter_editmode=False, + location=(0, 0, 0), + rotation=(0, 0, 0), + ) + + # Change Curve name to deform adds variable curv + curv = bpy.context.object + curv.name = "deform" + + # Inserts Torus primitive + bpy.ops.mesh.primitive_torus_add( + major_radius=1, + minor_radius=0.25, + major_segments=12, + minor_segments=4, + abso_major_rad=1, + abso_minor_rad=0.5, + ) + + # Positions Torus primitive to center of scene + bpy.context.active_object.location = 0.0, 0.0, 0.0 + + # Reseting Torus rotation in case of 'Align to view' option enabled + bpy.context.active_object.rotation_euler = 0.0, 0.0, 0.0 + + # Changes Torus name to chain adds variable tor + tor = bpy.context.object + tor.name = "chain" + + # Adds Array Modifier to tor + bpy.ops.object.modifier_add(type='ARRAY') + + # Adds subsurf modifier tor + bpy.ops.object.modifier_add(type='SUBSURF') + + # Smooths tor + bpy.ops.object.shade_smooth() + + # Select curv + sce = bpy.context.scene + sce.objects.active = curv + + # Toggle into editmode + bpy.ops.object.editmode_toggle() + + # TODO, may be better to move objects directly + # Translate curve object + bpy.ops.transform.translate( + value=(2, 0, 0), + constraint_axis=(True, False, False), + constraint_orientation='GLOBAL', + mirror=False, + proportional='DISABLED', + proportional_edit_falloff='SMOOTH', + proportional_size=1, + snap=False, + snap_target='CLOSEST', + snap_point=(0, 0, 0), + snap_align=False, + snap_normal=(0, 0, 0), + release_confirm=False, + ) + + # Toggle into objectmode + bpy.ops.object.editmode_toggle() + + # Select tor or chain + sce.objects.active = tor + + # Selects Array Modifier for editing + array = tor.modifiers['Array'] + + # Change Array Modifier Parameters + array.fit_type = 'FIT_CURVE' + array.curve = curv + array.offset_object = emp + array.use_object_offset = True + array.relative_offset_displace = 0.549, 0.0, 0.0 + + # Add curve modifier + bpy.ops.object.modifier_add(type='CURVE') + + # Selects Curve Modifier for editing + cur = tor.modifiers['Curve'] + + # Change Curve Modifier Parameters + cur.object = curv + + +class AddChain(Operator): + bl_idname = "mesh.primitive_chain_add" + bl_label = "Add Chain" + bl_description = ("Create a Chain segment with helper objects controlling modifiers:\n" + "1) A Curve Modifier Object (deform) for the length and shape,\n" + "Edit the Path to extend Chain Length\n" + "2) An Empty (rot_link) as an Array Offset for rotation") + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + try: + Add_Chain() + + except Exception as e: + self.report({'WARNING'}, + "Some operations could not be performed (See Console for more info)") + + print("\n[Add Advanced Objects]\nOperator: " + "mesh.primitive_chain_add\nError: {}".format(e)) + + return {'CANCELLED'} + + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(AddChain) + + +def unregister(): + bpy.utils.unregister_class(AddChain) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/oscurart_chain_maker.py b/add_advanced_objects_menu/oscurart_chain_maker.py new file mode 100644 index 00000000..6dcd6f80 --- /dev/null +++ b/add_advanced_objects_menu/oscurart_chain_maker.py @@ -0,0 +1,288 @@ +# ##### 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 ##### + +# TODO: find English versions of created object names + +bl_info = { + "name": "Oscurart Chain Maker", + "author": "Oscurart", + "version": (1, 1), + "blender": (2, 56, 0), + "location": "Add > Mesh > Oscurart Chain", + "description": "Create chain links from armatures", + "warning": "", + "wiki_url": "oscurart.blogspot.com", + "category": "Object"} + + +import bpy +from bpy.props import ( + BoolProperty, + FloatProperty, + ) +from bpy.types import Operator + + +def makeChain(self, context, mult, curverig): + + if not context.active_object.type == 'ARMATURE': + self.report({'WARNING'}, "Active Object must be an Armature") + return False + + bpy.ops.object.mode_set(mode='OBJECT') + VAR_SWITCH = abs(1) + ARMATURE = bpy.context.active_object + + def creahuesocero(hueso): + # create data to link + mesh = bpy.data.meshes.new("objectData" + str(hueso.name)) + object = bpy.data.objects.new("HardLink" + str(hueso.name), mesh) + mesh.from_pydata( + [(-0.04986128956079483, -0.6918092370033264, -0.17846597731113434), + (-0.04986128956079483, -0.6918091773986816, 0.17846640944480896), + (-0.049861326813697815, -0.154555082321167, 0.17846627533435822), + (-0.049861326813697815, -0.15455523133277893, -0.17846614122390747), + (-0.04986133798956871, -0.03475356101989746, 0.25805795192718506), + (-0.04986133798956871, -0.03475397825241089, -0.25805795192718506), + (-0.049861278384923935, -0.8116106986999512, -0.2580576539039612), + (-0.049861278384923935, -0.8116104602813721, 0.25805822014808655), + (-0.04986128211021423, -0.7692053318023682, 2.6668965347198537e-07), + (-0.04986127093434334, -0.923523485660553, 2.7834033744511544e-07), + (-0.04986133426427841, -0.0771591067314148, 3.5627678585115063e-08), + (-0.04986134544014931, 0.0771591067314148, -3.5627678585115063e-08), + (0.04986133798956871, -0.03475397825241089, -0.25805795192718506), + (0.04986133053898811, 0.0771591067314148, -3.5627678585115063e-08), + (0.04986133798956871, -0.03475356101989746, 0.25805795192718506), + (0.04986134544014931, -0.15455523133277893, -0.17846614122390747), + (0.04986134544014931, -0.0771591067314148, 3.5627678585115063e-08), + (0.04986134544014931, -0.154555082321167, 0.17846627533435822), + (0.049861397594213486, -0.8116106986999512, -0.2580576539039612), + (0.04986140504479408, -0.923523485660553, 2.7834033744511544e-07), + (0.049861397594213486, -0.8116104602813721, 0.25805822014808655), + (0.04986139014363289, -0.6918091773986816, 0.17846640944480896), + (0.04986139014363289, -0.7692053318023682, 2.6668965347198537e-07), + (0.04986139014363289, -0.6918092370033264, -0.17846597731113434)], + [(1, 2), (0, 3), (3, 5), (2, 4), (0, 6), (5, 6), (1, 7), (4, 7), (0, 8), (1, 8), + (7, 9), (6, 9), (8, 9), (2, 10), (3, 10), (4, 11), (5, 11), (10, 11), (5, 12), + (12, 13), (11, 13), (13, 14), (4, 14), (10, 16), (15, 16), (3, 15), (2, 17), + (16, 17), (9, 19), (18, 19), (6, 18), (7, 20), (19, 20), (8, 22), (21, 22), + (1, 21), (0, 23), (22, 23), (14, 20), (12, 18), (15, 23), (17, 21), (12, 15), + (13, 16), (14, 17), (20, 21), (19, 22), (18, 23)], + [(6, 0, 3, 5), (1, 7, 4, 2), (0, 6, 9, 8), (8, 9, 7, 1), (2, 4, 11, 10), (10, 11, 5, 3), + (11, 13, 12, 5), (4, 14, 13, 11), (3, 15, 16, 10), (10, 16, 17, 2), (6, 18, 19, 9), + (9, 19, 20, 7), (1, 21, 22, 8), (23, 0, 8, 22), (7, 20, 14, 4), (5, 12, 18, 6), + (0, 23, 15, 3), (2, 17, 21, 1), (16, 15, 12, 13), (17, 16, 13, 14), (22, 21, 20, 19), + (23, 22, 19, 18), (21, 17, 14, 20), (15, 23, 18, 12)] + ) + mesh.validate() + bpy.context.scene.objects.link(object) + # scale to the bone + bpy.data.objects["HardLink" + str(hueso.name)].scale = (hueso.length * mult, + hueso.length * mult, + hueso.length * mult) + # Parent Objects + bpy.data.objects["HardLink" + str(hueso.name)].parent = ARMATURE + bpy.data.objects["HardLink" + str(hueso.name)].parent_type = 'BONE' + bpy.data.objects["HardLink" + str(hueso.name)].parent_bone = hueso.name + + def creahuesonoventa(hueso): + # create data to link + mesh = bpy.data.meshes.new("objectData" + str(hueso.name)) + object = bpy.data.objects.new("NewLink" + str(hueso.name), mesh) + mesh.from_pydata( + [(0.1784660965204239, -0.6918091773986816, -0.049861203879117966), + (-0.1784662902355194, -0.6918091773986816, -0.04986126348376274), + (-0.17846627533435822, -0.1545550525188446, -0.04986134544014931), + (0.17846617102622986, -0.15455520153045654, -0.04986128583550453), + (-0.25805795192718506, -0.03475359082221985, -0.049861375242471695), + (0.25805795192718506, -0.034753888845443726, -0.04986129328608513), + (0.2580578327178955, -0.8116105794906616, -0.04986117407679558), + (-0.2580580413341522, -0.8116105198860168, -0.049861256033182144), + (-9.672299938756623e-08, -0.7692052721977234, -0.04986122250556946), + (-8.99775329799013e-08, -0.923523485660553, -0.04986120015382767), + (-7.764004550381287e-09, -0.07715904712677002, -0.049861326813697815), + (4.509517737005808e-08, 0.0771591067314148, -0.049861349165439606), + (0.25805795192718506, -0.034753888845443726, 0.049861375242471695), + (-2.2038317837314025e-08, 0.0771591067314148, 0.049861326813697815), + (-0.25805795192718506, -0.03475359082221985, 0.04986129328608513), + (0.17846617102622986, -0.15455520153045654, 0.04986138269305229), + (-1.529285498236277e-08, -0.07715907692909241, 0.049861352890729904), + (-0.17846627533435822, -0.1545550525188446, 0.049861323088407516), + (0.2580578029155731, -0.8116105794906616, 0.049861494451761246), + (-1.5711103173998708e-07, -0.923523485660553, 0.04986147582530975), + (-0.2580580711364746, -0.8116105198860168, 0.04986141249537468), + (-0.1784663051366806, -0.6918091773986816, 0.049861419945955276), + (-1.340541757599567e-07, -0.7692052721977234, 0.049861449748277664), + (0.1784660816192627, -0.6918091773986816, 0.04986146464943886)], + [(1, 2), (0, 3), (3, 5), (2, 4), (0, 6), (5, 6), (1, 7), (4, 7), (0, 8), + (1, 8), (7, 9), (6, 9), (8, 9), (2, 10), (3, 10), (4, 11), (5, 11), (10, 11), + (5, 12), (12, 13), (11, 13), (13, 14), (4, 14), (10, 16), (15, 16), (3, 15), + (2, 17), (16, 17), (9, 19), (18, 19), (6, 18), (7, 20), (19, 20), (8, 22), + (21, 22), (1, 21), (0, 23), (22, 23), (14, 20), (12, 18), (15, 23), (17, 21), + (12, 15), (13, 16), (14, 17), (20, 21), (19, 22), (18, 23)], + [(6, 0, 3, 5), (1, 7, 4, 2), (0, 6, 9, 8), (8, 9, 7, 1), (2, 4, 11, 10), + (10, 11, 5, 3), (11, 13, 12, 5), (4, 14, 13, 11), (3, 15, 16, 10), (10, 16, 17, 2), + (6, 18, 19, 9), (9, 19, 20, 7), (1, 21, 22, 8), (23, 0, 8, 22), (7, 20, 14, 4), + (5, 12, 18, 6), (0, 23, 15, 3), (2, 17, 21, 1), (16, 15, 12, 13), (17, 16, 13, 14), + (22, 21, 20, 19), (23, 22, 19, 18), (21, 17, 14, 20), (15, 23, 18, 12)] + ) + mesh.validate() + bpy.context.scene.objects.link(object) + # scale to the bone + bpy.data.objects["NewLink" + str(hueso.name)].scale = (hueso.length * mult, + hueso.length * mult, + hueso.length * mult) + # Parent objects + bpy.data.objects["NewLink" + str(hueso.name)].parent = ARMATURE + bpy.data.objects["NewLink" + str(hueso.name)].parent_type = 'BONE' + bpy.data.objects["NewLink" + str(hueso.name)].parent_bone = hueso.name + + for hueso in bpy.context.active_object.pose.bones: + if VAR_SWITCH == 1: + creahuesocero(hueso) + else: + creahuesonoventa(hueso) + if VAR_SWITCH == 1: + VAR_SWITCH = 0 + else: + VAR_SWITCH = 1 + + # if curve rig is activated + if curverig is True: + # variables + LISTA_POINTC = [] + ACTARM = bpy.context.active_object + + # create data and link the object to the scene + crv = bpy.data.curves.new("CurvaCable", "CURVE") + obCable = bpy.data.objects.new("Cable", crv) + bpy.context.scene.objects.link(obCable) + + # set the attributes + crv.dimensions = "3D" + crv.resolution_u = 10 + crv.resolution_v = 10 + crv.twist_mode = "MINIMUM" + + # create the list of tail and head coordinates + LISTA_POINTC.append(( + ACTARM.data.bones[0].head_local[0], + ACTARM.data.bones[0].head_local[1], + ACTARM.data.bones[0].head_local[2], 1 + )) + + for hueso in ACTARM.data.bones: + LISTA_POINTC.append(( + hueso.tail_local[0], + hueso.tail_local[1], + hueso.tail_local[2], 1 + )) + + # create the Spline + spline = crv.splines.new("NURBS") + lencoord = len(LISTA_POINTC) + rango = range(lencoord) + spline.points.add(lencoord - 1) + + for punto in rango: + spline.points[punto].co = LISTA_POINTC[punto] + + # set the endpoint + bpy.data.objects['Cable'].data.splines[0].use_endpoint_u = True + # select the curve + bpy.ops.object.select_all(action='DESELECT') + bpy.data.objects['Cable'].select = 1 + bpy.context.scene.objects.active = bpy.data.objects['Cable'] + # switch to Edit mode + bpy.ops.object.mode_set(mode='EDIT') + + # create hooks + POINTSTEP = 0 + for POINT in bpy.data.objects['Cable'].data.splines[0].points: + bpy.ops.curve.select_all(action="DESELECT") + bpy.data.objects['Cable'].data.splines[0].points[POINTSTEP].select = 1 + bpy.ops.object.hook_add_newob() + POINTSTEP += 1 + + # Objects selection step + bpy.ops.object.mode_set(mode='OBJECT') + bpy.ops.object.select_all(action='DESELECT') + ACTARM.select = 1 + bpy.context.scene.objects.active = bpy.data.objects['Armature'] + bpy.ops.object.mode_set(mode='POSE') + bpy.ops.pose.select_all(action='DESELECT') + ACTARM.data.bones[-1].select = 1 + ACTARM.data.bones.active = ACTARM.data.bones[-1] + + # set IK Spline + bpy.ops.pose.constraint_add_with_targets(type='SPLINE_IK') + ACTARM.pose.bones[-1].constraints['Spline IK'].target = bpy.data.objects['Cable'] + ACTARM.pose.bones[-1].constraints['Spline IK'].chain_count = 100 + bpy.context.active_object.pose.bones[-1].constraints['Spline IK'].use_y_stretch = False + # return to Object mode + bpy.ops.object.mode_set(mode='OBJECT') + + +class MESH_OT_primitive_oscurart_chain_add(Operator): + bl_idname = "mesh.primitive_oscurart_chain_add" + bl_label = "Chain to Bones" + bl_description = ("Add Chain Parented to an Existing Armature\n" + "The Active/Last Selected Object must be an Armature") + bl_options = {'REGISTER', 'UNDO'} + + curverig = BoolProperty( + name="Curve Rig", + default=False + ) + multiplier = FloatProperty( + name="Scale", + default=1, + min=0.01, max=100.0 + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj is not None and obj.type == "ARMATURE") + + def execute(self, context): + try: + makeChain(self, context, self.multiplier, self.curverig) + + except Exception as e: + self.report({'WARNING'}, + "Some operations could not be performed (See Console for more info)") + + print("\n[Add Advanced Objects]\nOperator: " + "mesh.primitive_oscurart_chain_add\nError: {}".format(e)) + + return {'CANCELLED'} + + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(MESH_OT_primitive_oscurart_chain_add) + + +def unregister(): + bpy.utils.unregister_class(MESH_OT_primitive_oscurart_chain_add) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/pixelate_3d.py b/add_advanced_objects_menu/pixelate_3d.py new file mode 100644 index 00000000..d2b28971 --- /dev/null +++ b/add_advanced_objects_menu/pixelate_3d.py @@ -0,0 +1,114 @@ +# gpl author: liero +# very simple 'pixelization' or 'voxelization' engine # + +bl_info = { + "name": "3D Pixelate", + "author": "liero", + "version": (0, 5, 2), + "blender": (2, 74, 0), + "location": "View3D > Tool Shelf", + "description": "Creates a 3d pixelated version of the object", + "category": "Object"} + +# Note: winmgr properties are moved into __init__ +# search for patterns advanced_objects and adv_obj + +import bpy +from bpy.types import Operator + + +def pix(obj): + sce = bpy.context.scene + props = sce.advanced_objects + obj.hide = obj.hide_render = True + mes = obj.to_mesh(sce, True, 'RENDER') + mes.transform(obj.matrix_world) + dup = bpy.data.objects.new('dup', mes) + sce.objects.link(dup) + dup.dupli_type = 'VERTS' + sce.objects.active = dup + bpy.ops.object.mode_set() + ver = mes.vertices + + for i in range(250): + fin = True + for i in dup.data.edges: + d = ver[i.vertices[0]].co - ver[i.vertices[1]].co + if d.length > props.pixelate_3d_size: + ver[i.vertices[0]].select = True + ver[i.vertices[1]].select = True + fin = False + bpy.ops.object.editmode_toggle() + bpy.ops.mesh.subdivide(number_cuts=1, smoothness=props.pixelate_3d_smooth) + bpy.ops.mesh.select_all(action='DESELECT') + bpy.ops.object.editmode_toggle() + if fin: + break + + for i in ver: + for n in range(3): + i.co[n] -= (.001 + i.co[n]) % props.pixelate_3d_size + + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.mesh.remove_doubles(threshold=0.0001) + bpy.ops.mesh.delete(type='EDGE_FACE') + bpy.ops.object.mode_set() + sca = props.pixelate_3d_size * (100 - props.pixelate_3d_gap) * .005 + bpy.ops.mesh.primitive_cube_add(layers=[True] + [False] * 19) + bpy.ops.transform.resize(value=[sca] * 3) + bpy.context.scene.objects.active = dup + bpy.ops.object.parent_set(type='OBJECT') + + +class Pixelate(Operator): + bl_idname = "object.pixelate" + bl_label = "Pixelate Object" + bl_description = ("Create a 3d pixelated version of the object\n" + "using a Duplivert Box around each copied vertex\n" + "With high poly objects, it can take some time\n" + "Needs an existing Active Mesh Object") + bl_options = {'REGISTER', 'UNDO'} + + @classmethod + def poll(cls, context): + return (context.active_object and + context.active_object.type == 'MESH' and + context.mode == 'OBJECT') + + def draw(self, context): + layout = self.layout + adv_obj = context.scene.advanced_objects + + col = layout.column(align=True) + col.prop(adv_obj, "pixelate_size") + col.prop(adv_obj, "pixelate_gap") + layout.prop(adv_obj, "pixelate_smooth") + + def execute(self, context): + objeto = context.active_object + try: + pix(objeto) + + except Exception as e: + self.report({'WARNING'}, + "Some operations could not be performed (See Console for more info)") + + print("\n[Add Advanced Objects]\nOperator: " + "object.pixelate\nError: {}".format(e)) + + return {'CANCELLED'} + + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(Pixelate) + + +def unregister(): + bpy.utils.unregister_class(Pixelate) + + +if __name__ == '__main__': + register() diff --git a/add_advanced_objects_menu/random_box_structure.py b/add_advanced_objects_menu/random_box_structure.py new file mode 100644 index 00000000..fa4b6497 --- /dev/null +++ b/add_advanced_objects_menu/random_box_structure.py @@ -0,0 +1,201 @@ +# gpl: author Dannyboy + +bl_info = { + "name": "Add Random Box Structure", + "author": "Dannyboy", + "version": (1, 0, 1), + "location": "View3D > Add > Make Box Structure", + "description": "Fill selected box shaped meshes with randomly sized cubes", + "warning": "", + "wiki_url": "", + "tracker_url": "dannyboypython.blogspot.com", + "category": "Object"} + +import bpy +import random +from bpy.types import Operator +from bpy.props import ( + BoolProperty, + FloatProperty, + FloatVectorProperty, + IntProperty, + ) + + +class makestructure(Operator): + bl_idname = "object.make_structure" + bl_label = "Add Random Box Structure" + bl_description = ("Create a randomized structure made of boxes\n" + "with various control parameters\n" + "Needs an existing Active Mesh Object") + bl_options = {'REGISTER', 'UNDO'} + + dc = BoolProperty( + name="Delete Base Mesh(es)", + default=True + ) + wh = BoolProperty( + name="Stay Within Bounds", + description="Keeps cubes from exceeding base mesh bounds", + default=True + ) + uf = BoolProperty( + name="Uniform Cube Quantity", + default=False + ) + qn = IntProperty( + name="Cube Quantity", + default=10, + min=1, max=1500 + ) + mn = FloatVectorProperty( + name="Min Scales", + default=(0.1, 0.1, 0.1), + subtype='XYZ' + ) + mx = FloatVectorProperty( + name="Max Scales", + default=(2.0, 2.0, 2.0), + subtype='XYZ' + ) + lo = FloatVectorProperty( + name="XYZ Offset", + default=(0.0, 0.0, 0.0), + subtype='XYZ' + ) + rsd = FloatProperty( + name="Random Seed", + default=1 + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return obj is not None and obj.type == "MESH" and obj.mode == "OBJECT" + + def draw(self, context): + layout = self.layout + + box = layout.box() + box.label(text="Options:") + box.prop(self, "dc") + box.prop(self, "wh") + box.prop(self, "uf") + + box = layout.box() + box.label(text="Parameters:") + box.prop(self, "qn") + box.prop(self, "mn") + box.prop(self, "mx") + box.prop(self, "lo") + box.prop(self, "rsd") + + def execute(self, context): + rsdchange = self.rsd + oblst = [] + uvyes = 0 + bpy.ops.group.create(name='Cubagrouper') + bpy.ops.group.objects_remove() + + for ob in bpy.context.selected_objects: + oblst.append(ob) + + for obj in oblst: + bpy.ops.object.select_pattern(pattern=obj.name) # Select base mesh + bpy.context.scene.objects.active = obj + if obj.data.uv_layers[:] != []: + uvyes = 1 + else: + uvyes = 0 + bpy.ops.object.group_link(group='Cubagrouper') + dim = obj.dimensions + rot = obj.rotation_euler + if self.uf is True: + area = dim.x * dim.y * dim.z + else: + area = 75 + + for cube in range(round((area / 75) * self.qn)): + random.seed(rsdchange) + pmn = self.mn # Proxy values + pmx = self.mx + if self.wh is True: + if dim.x < pmx.x: # Keeping things from exceeding proper size + pmx.x = dim.x + if dim.y < pmx.y: + pmx.y = dim.y + if dim.z < pmx.z: + pmx.z = dim.z + if 0.0 > pmn.x: # Keeping things from going under zero + pmn.x = 0.0 + if 0.0 > pmn.y: + pmn.y = 0.0 + if 0.0 > pmn.z: + pmn.z = 0.0 + sx = (random.random() * (pmx.x - pmn.x)) + pmn.x # Just changed self.mx and .mn to pmx. + sy = (random.random() * (pmx.y - pmn.y)) + pmn.y + sz = (random.random() * (pmx.z - pmn.z)) + pmn.z + if self.wh is True: # This keeps the cubes within the base mesh + ex = (random.random() * (dim.x - sx)) - ((dim.x - sx) / 2) + obj.location.x + wy = (random.random() * (dim.y - sy)) - ((dim.y - sy) / 2) + obj.location.y + ze = (random.random() * (dim.z - sz)) - ((dim.z - sz) / 2) + obj.location.z + elif self.wh is False: + ex = (random.random() * dim.x) - (dim.x / 2) + obj.location.x + wy = (random.random() * dim.y) - (dim.y / 2) + obj.location.y + ze = (random.random() * dim.z) - (dim.z / 2) + obj.location.z + bpy.ops.mesh.primitive_cube_add( + radius=0.5, location=(ex + self.lo.x, wy + self.lo.y, ze + self.lo.z) + ) + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.mesh.select_all(action='SELECT') + bpy.ops.transform.resize( + value=(sx, sy, sz), constraint_axis=(True, True, True), + constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True + ) + bpy.ops.object.mode_set(mode='OBJECT') + select = bpy.context.object # This is used to keep something selected for poll() + bpy.ops.object.group_link(group='Cubagrouper') + rsdchange += 3 + bpy.ops.object.select_grouped(type='GROUP') + bpy.ops.transform.rotate( + value=rot[0], axis=(1, 0, 0), constraint_axis=(False, False, False), + constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True + ) + bpy.ops.transform.rotate( + value=rot[1], axis=(0, 1, 0), constraint_axis=(False, False, False), + constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True + ) + bpy.ops.transform.rotate( + value=rot[2], axis=(0, 0, 1), constraint_axis=(False, False, False), + constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True + ) + bpy.context.scene.objects.active = obj # Again needed to avoid poll() taking me down + bpy.ops.object.make_links_data(type='MODIFIERS') + bpy.ops.object.make_links_data(type='MATERIAL') + + if uvyes == 1: + bpy.ops.object.join_uvs() + + bpy.ops.group.objects_remove() + bpy.context.scene.objects.active = select + + if self.dc is True: + bpy.context.scene.objects.unlink(obj) + + return {'FINISHED'} + + +def register(): + bpy.utils.register_class(makestructure) + + +def unregister(): + bpy.utils.unregister_class(makestructure) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/rope_alpha.py b/add_advanced_objects_menu/rope_alpha.py new file mode 100644 index 00000000..904168a1 --- /dev/null +++ b/add_advanced_objects_menu/rope_alpha.py @@ -0,0 +1,832 @@ +# Copyright (c) 2012 Jorge Hernandez - Melendez + +# ##### 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 ##### + +# TODO : prop names into English, add missing tooltips + +bl_info = { + "name": "Rope Creator", + "description": "Dynamic rope (with cloth) creator", + "author": "Jorge Hernandez - Melenedez", + "version": (0, 2, 2), + "blender": (2, 7, 3), + "location": "Left Toolbar > ClothRope", + "warning": "", + "wiki_url": "", + "category": "Add Mesh" +} + + +import bpy +from bpy.types import Operator +from bpy.props import ( + BoolProperty, + FloatProperty, + IntProperty, + ) + + +def desocultar(quien): + if quien == "todo": + for ob in bpy.data.objects: + ob.hide = False + else: + bpy.data.objects[quien].hide = False + + +def deseleccionar_todo(): + bpy.ops.object.select_all(action='DESELECT') + + +def seleccionar_todo(): + bpy.ops.object.select_all(action='SELECT') + + +def salir_de_editmode(): + if bpy.context.mode in ["EDIT", "EDIT_MESH", "EDIT_CURVE"]: + bpy.ops.object.mode_set(mode='OBJECT') + + +# Clear scene: +def reset_scene(): + desocultar("todo") + # playback to the start + bpy.ops.screen.frame_jump(end=False) + try: + salir_de_editmode() + except: + pass + try: + area = bpy.context.area + # expand everything in the outliner to be able to select children + old_type = area.type + area.type = 'OUTLINER' + bpy.ops.outliner.expanded_toggle() + + # restore the original context + area.type = old_type + + seleccionar_todo() + bpy.ops.object.delete(use_global=False) + + except Exception as e: + print("\n[rope_alpha]\nfunction: reset_scene\nError: %s" % e) + + +def entrar_en_editmode(): + if bpy.context.mode == "OBJECT": + bpy.ops.object.mode_set(mode='EDIT') + + +def select_all_in_edit_mode(ob): + if ob.mode != 'EDIT': + entrar_en_editmode() + bpy.ops.mesh.select_all(action="DESELECT") + bpy.context.tool_settings.mesh_select_mode = (True, False, False) + salir_de_editmode() + for v in ob.data.vertices: + if not v.select: + v.select = True + entrar_en_editmode() + + +def deselect_all_in_edit_mode(ob): + if ob.mode != 'EDIT': + entrar_en_editmode() + bpy.ops.mesh.select_all(action="DESELECT") + bpy.context.tool_settings.mesh_select_mode = (True, False, False) + salir_de_editmode() + for v in ob.data.vertices: + if not v.select: + v.select = False + entrar_en_editmode() + + +def which_vertex_are_selected(ob): + for v in ob.data.vertices: + if v.select: + print(str(v.index)) + print("Vertex " + str(v.index) + " is selected") + + +def seleccionar_por_nombre(nombre): + scn = bpy.context.scene + bpy.data.objects[nombre].select = True + + scn.objects.active = bpy.data.objects[nombre] + + +def deseleccionar_por_nombre(nombre): + bpy.data.objects[nombre].select = False + + +def crear_vertices(ob): + ob.data.vertices.add(1) + ob.data.update + + +def borrar_elementos_seleccionados(tipo): + if tipo == "vertices": + bpy.ops.mesh.delete(type='VERT') + + +def obtener_coords_vertex_seleccionados(): + coordenadas_de_vertices = [] + for ob in bpy.context.selected_objects: + if ob.type == 'MESH': + for v in ob.data.vertices: + if v.select: + coordenadas_de_vertices.append([v.co[0], v.co[1], v.co[2]]) + return coordenadas_de_vertices[0] + + +def crear_locator(pos): + bpy.ops.object.empty_add( + type='PLAIN_AXES', radius=1, view_align=False, + location=(pos[0], pos[1], pos[2]), + layers=(True, False, False, False, False, False, False, + False, False, False, False, False, False, False, + False, False, False, False, False, False) + ) + + +def extruir_vertices(longitud, cuantos_segmentos): + bpy.ops.mesh.extrude_region_move( + MESH_OT_extrude_region={"mirror": False}, + TRANSFORM_OT_translate={ + "value": (longitud / cuantos_segmentos, 0, 0), + "constraint_axis": (True, False, False), + "constraint_orientation": 'GLOBAL', "mirror": False, + "proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH', + "proportional_size": 1, "snap": False, "snap_target": 'CLOSEST', + "snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0), + "gpencil_strokes": False, "texture_space": False, + "remove_on_cancel": False, "release_confirm": False + } + ) + + +def select_all_vertex_in_curve_bezier(bc): + for i in range(len(bc.data.splines[0].points)): + bc.data.splines[0].points[i].select = True + + +def deselect_all_vertex_in_curve_bezier(bc): + for i in range(len(bc.data.splines[0].points)): + bc.data.splines[0].points[i].select = False + + +def ocultar_relationships(): + for area in bpy.context.screen.areas: + if area.type == 'VIEW_3D': + area.spaces[0].show_relationship_lines = False + + +class ClothRope(Operator): + bl_idname = "clot.rope" + bl_label = "Rope Cloth" + bl_description = ("Create a new Scene with a Cloth modifier\n" + "Rope Simulation with hooked Helper Objects") + + ropelenght = IntProperty( + name="Rope Length", + description="Length of the generated Rope", + default=5 + ) + ropesegments = IntProperty( + name="Rope Segments", + description="Number of the Rope Segments", + default=5 + ) + qcr = IntProperty( + name="Collision Quality", + description="Rope's Cloth modifier collsion quality", + min=1, max=20, + default=20 + ) + substeps = IntProperty( + name="Rope Substeps", + description="Rope's Cloth modifier quality", + min=4, max=80, + default=50 + ) + resrope = IntProperty( + name="Rope Resolution", + description="Rope's Bevel resolution", + default=5 + ) + radiusrope = FloatProperty( + name="Radius", + description="Rope's Radius", + min=0.04, max=1, + default=0.04 + ) + hide_emptys = BoolProperty( + name="Hide Empties", + description="Hide Helper Objects", + default=False + ) + + def execute(self, context): + # add a new scene + bpy.ops.scene.new(type="NEW") + scene = bpy.context.scene + scene.name = "Test Rope" + seleccionar_todo() + longitud = self.ropelenght + + # For the middle to have x segments between the first and + # last point, must add 1 to the quantity: + cuantos_segmentos = self.ropesegments + 1 + calidad_de_colision = self.qcr + substeps = self.substeps + deseleccionar_todo() + # collect the possible empties that already exist in the data + empties_prev = [obj.name for obj in bpy.data.objects if obj.type == "EMPTY"] + + # create an empty that will be the parent of everything + bpy.ops.object.empty_add( + type='SPHERE', radius=1, view_align=False, location=(0, 0, 0), + layers=(True, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, + False, False, False, False) + ) + ob = bpy.context.selected_objects[0] + ob.name = "Rope" + # .001 and friends + rope_name = ob.name + deseleccionar_todo() + + # create a plane and delete it + bpy.ops.mesh.primitive_plane_add( + radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0), + layers=(True, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, + False, False, False) + ) + ob = bpy.context.selected_objects[0] + # rename: + ob.name = "cuerda" + # .001 and friends + cuerda_1_name = ob.name + + entrar_en_editmode() # enter edit mode + select_all_in_edit_mode(ob) + + borrar_elementos_seleccionados("vertices") + salir_de_editmode() # leave edit mode + crear_vertices(ob) # create a vertex + + # Creating a Group for the PIN + # Group contains the vertices of the pin and the Group.001 contains the single main line + entrar_en_editmode() # enter edit mode + bpy.ops.object.vertex_group_add() # create a group + select_all_in_edit_mode(ob) + bpy.ops.object.vertex_group_assign() # assign it + + salir_de_editmode() # leave edit mode + ob.vertex_groups[0].name = "Pin" + deseleccionar_todo() + seleccionar_por_nombre(cuerda_1_name) + + # extrude vertices: + for i in range(cuantos_segmentos): + entrar_en_editmode() + extruir_vertices(longitud, cuantos_segmentos) + # delete the PIN group + bpy.ops.object.vertex_group_remove_from() + # get the direction to create the locator on it's position + pos = obtener_coords_vertex_seleccionados() + + salir_de_editmode() # leave edit mode + # create locator at position + crear_locator(pos) + deseleccionar_todo() + seleccionar_por_nombre(cuerda_1_name) + deseleccionar_todo() + + seleccionar_por_nombre(cuerda_1_name) # select the rope + entrar_en_editmode() + + pos = obtener_coords_vertex_seleccionados() # get their positions + salir_de_editmode() + # create the last locator + crear_locator(pos) + deseleccionar_todo() + seleccionar_por_nombre(cuerda_1_name) + entrar_en_editmode() # enter edit mode + bpy.ops.object.vertex_group_add() # Creating Master guide group + select_all_in_edit_mode(ob) + bpy.ops.object.vertex_group_assign() # and assing it + ob.vertex_groups[1].name = "Guide_rope" + + # extrude the Curve so it has a minumum thickness for collide + bpy.ops.mesh.extrude_region_move( + MESH_OT_extrude_region={"mirror": False}, + TRANSFORM_OT_translate={ + "value": (0, 0.005, 0), "constraint_axis": (False, True, False), + "constraint_orientation": 'GLOBAL', "mirror": False, + "proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH', + "proportional_size": 1, "snap": False, "snap_target": 'CLOSEST', + "snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0), + "gpencil_strokes": False, "texture_space": False, + "remove_on_cancel": False, "release_confirm": False + } + ) + bpy.ops.object.vertex_group_remove_from() + deselect_all_in_edit_mode(ob) + salir_de_editmode() + bpy.ops.object.modifier_add(type='CLOTH') + bpy.context.object.modifiers["Cloth"].settings.use_pin_cloth = True + bpy.context.object.modifiers["Cloth"].settings.vertex_group_mass = "Pin" + bpy.context.object.modifiers["Cloth"].collision_settings.collision_quality = calidad_de_colision + bpy.context.object.modifiers["Cloth"].settings.quality = substeps + + # Duplicate to convert into Curve: + # select the vertices that are the part of the Group.001 + seleccionar_por_nombre(cuerda_1_name) + entrar_en_editmode() + bpy.ops.mesh.select_all(action="DESELECT") + bpy.context.tool_settings.mesh_select_mode = (True, False, False) + salir_de_editmode() + gi = ob.vertex_groups["Guide_rope"].index # get group index + + for v in ob.data.vertices: + for g in v.groups: + if g.group == gi: # compare with index in VertexGroupElement + v.select = True + + # now we have to make a table of names of cuerdas to see which one will be new + cuerda_names = [obj.name for obj in bpy.data.objects if "cuerda" in obj.name] + + entrar_en_editmode() + + # we already have the selected guide: + # duplicate it: + bpy.ops.mesh.duplicate_move( + MESH_OT_duplicate={"mode": 1}, + TRANSFORM_OT_translate={ + "value": (0, 0, 0), "constraint_axis": (False, False, False), + "constraint_orientation": 'GLOBAL', "mirror": False, + "proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH', + "proportional_size": 1, "snap": False, "snap_target": 'CLOSEST', + "snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0), + "gpencil_strokes": False, "texture_space": False, + "remove_on_cancel": False, "release_confirm": False + } + ) + # separate the selections: + bpy.ops.mesh.separate(type='SELECTED') + salir_de_editmode() + deseleccionar_todo() + + cuerda_2_name = "cuerda.001" + test = [] + for obj in bpy.data.objects: + if "cuerda" in obj.name and obj.name not in cuerda_names: + cuerda_2_name = obj.name + test.append(obj.name) + + seleccionar_por_nombre(cuerda_2_name) + + # from the newly created curve remove the Cloth: + bpy.ops.object.modifier_remove(modifier="Cloth") + # convert the Curve: + bpy.ops.object.convert(target='CURVE') + + # all Empties that are not previously present + emptys = [] + for eo in bpy.data.objects: + if eo.type == 'EMPTY' and eo.name not in empties_prev: + if eo.name != rope_name: + emptys.append(eo) + + # select and deselect: + bc = bpy.data.objects[cuerda_2_name] + n = 0 + + for e in emptys: + deseleccionar_todo() + seleccionar_por_nombre(e.name) + seleccionar_por_nombre(bc.name) + entrar_en_editmode() + deselect_all_vertex_in_curve_bezier(bc) + bc.data.splines[0].points[n].select = True + bpy.ops.object.hook_add_selob(use_bone=False) + salir_de_editmode() + n = n + 1 + + ob = bpy.data.objects[cuerda_1_name] + n = 0 + + for e in emptys: + deseleccionar_todo() + seleccionar_por_nombre(e.name) + seleccionar_por_nombre(ob.name) + entrar_en_editmode() + bpy.ops.mesh.select_all(action="DESELECT") + bpy.context.tool_settings.mesh_select_mode = (True, False, False) + salir_de_editmode() + + for v in ob.data.vertices: + if v.select: + v.select = False + ob.data.vertices[n].select = True + entrar_en_editmode() + bpy.ops.object.vertex_parent_set() + + salir_de_editmode() + n = n + 1 + + # hide the Empties: + deseleccionar_todo() + + # all parented to the spherical empty: + seleccionar_por_nombre(cuerda_2_name) + seleccionar_por_nombre(cuerda_1_name) + seleccionar_por_nombre(rope_name) + bpy.ops.object.parent_set(type='OBJECT', keep_transform=True) + deseleccionar_todo() + + # do not display the relations + ocultar_relationships() + seleccionar_por_nombre(cuerda_2_name) + + # curved rope settings: + bpy.context.object.data.fill_mode = 'FULL' + bpy.context.object.data.bevel_depth = self.radiusrope + bpy.context.object.data.bevel_resolution = self.resrope + + return {'FINISHED'} + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self, width=350) + + def draw(self, context): + layout = self.layout + box = layout.box() + col = box.column(align=True) + + col.label("Rope settings:") + rowsub0 = col.row() + rowsub0.prop(self, "ropelenght", text="Length") + rowsub0.prop(self, "ropesegments", text="Segments") + rowsub0.prop(self, "radiusrope", text="Radius") + + col.label("Quality Settings:") + col.prop(self, "resrope", text="Resolution curve") + col.prop(self, "qcr", text="Quality Collision") + col.prop(self, "substeps", text="Substeps") + + +class BallRope(Operator): + bl_idname = "ball.rope" + bl_label = "Wrecking Ball" + bl_description = ("Create a new Scene with a Rigid Body simulation of\n" + "Wrecking Ball on a rope") + + # defaults rope ball + ropelenght2 = IntProperty( + name="Rope Length", + description="Length of the Wrecking Ball rope", + default=10 + ) + ropesegments2 = IntProperty( + name="Rope Segments", + description="Number of the Wrecking Ball rope segments", + min=0, max=999, + default=6 + ) + radiuscubes = FloatProperty( + name="Cube Radius", + description="Size of the Linked Cubes helpers", + default=0.5 + ) + radiusrope = FloatProperty( + name="Rope Radius", + description="Radius of the Rope", + default=0.4 + ) + worldsteps = IntProperty( + name="World Steps", + description="Rigid Body Solver world steps per second (update)", + min=60, max=1000, + default=250 + ) + solveriterations = IntProperty( + name="Solver Iterations", + description="How many times the Rigid Body Solver should run", + min=10, max=100, + default=50 + ) + massball = IntProperty( + name="Ball Mass", + description="Mass of the Wrecking Ball", + default=1 + ) + resrope = IntProperty( + name="Resolution", + description="Rope resolution", + default=4 + ) + grados = FloatProperty( + name="Degrees", + description="Angle of the Wrecking Ball compared to the Ground Plane", + default=45 + ) + separacion = FloatProperty( + name="Link Cubes Gap", + description="Space between the Rope's Linked Cubes", + default=0.1 + ) + hidecubes = BoolProperty( + name="Hide Link Cubes", + description="Hide helper geometry for the Rope", + default=False + ) + + def execute(self, context): + world_steps = self.worldsteps + solver_iterations = self.solveriterations + longitud = self.ropelenght2 + + # make a + 2, so the segments will be between the two end points... + segmentos = self.ropesegments2 + 2 + offset_del_suelo = 1 + offset_del_suelo_real = (longitud / 2) + (segmentos / 2) + radio = self.radiuscubes + radiorope = self.radiusrope + masa = self.massball + resolucion = self.resrope + rotrope = self.grados + separation = self.separacion + hidecubeslinks = self.hidecubes + + # add new scene + bpy.ops.scene.new(type="NEW") + scene = bpy.context.scene + scene.name = "Test Ball" + + # collect the possible constraint empties that already exist in the data + constraint_prev = [obj.name for obj in bpy.data.objects if + obj.type == "EMPTY" and "Constraint" in obj.name] + # floor: + bpy.ops.mesh.primitive_cube_add( + radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0), + layers=(True, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, + False, False, False) + ) + bpy.context.object.scale.x = 10 + longitud + bpy.context.object.scale.y = 10 + longitud + bpy.context.object.scale.z = 0.05 + bpy.context.object.name = "groundplane" + # The secret agents .001, 002 etc. + groundplane_name = bpy.context.object.name + + bpy.ops.rigidbody.objects_add(type='PASSIVE') + + # create the first cube: + cuboslink = [] + n = 0 + for i in range(segmentos): + # if 0 start from 1 + if i == 0: + i = offset_del_suelo + else: # if it is not 0, add one so it doesn't step on the first one starting from 1 + i = i + offset_del_suelo + separacion = longitud * 2 / segmentos # distance between linked cubes + bpy.ops.mesh.primitive_cube_add( + radius=1, view_align=False, enter_editmode=False, + location=(0, 0, i * separacion), + layers=(True, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, + False, False, False, False) + ) + bpy.ops.rigidbody.objects_add(type='ACTIVE') + bpy.context.object.name = "CubeLink" + if n != 0: + bpy.context.object.draw_type = 'WIRE' + bpy.context.object.hide_render = True + n += 1 + bpy.context.object.scale.z = (longitud * 2) / (segmentos * 2) - separation + bpy.context.object.scale.x = radio + bpy.context.object.scale.y = radio + cuboslink.append(bpy.context.object) + + for i in range(len(cuboslink)): + deseleccionar_todo() + if i != len(cuboslink) - 1: + nombre1 = cuboslink[i] + nombre2 = cuboslink[i + 1] + seleccionar_por_nombre(nombre1.name) + seleccionar_por_nombre(nombre2.name) + bpy.ops.rigidbody.connect() + + # select by name + constraint_new = [ + obj.name for obj in bpy.data.objects if + obj.type == "EMPTY" and "Constraint" in obj.name and + obj.name not in constraint_prev + ] + + for names in constraint_new: + seleccionar_por_nombre(names) + + for c in bpy.context.selected_objects: + c.rigid_body_constraint.type = 'POINT' + deseleccionar_todo() + + # create a Bezier curve: + bpy.ops.curve.primitive_bezier_curve_add( + radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0), + layers=(True, False, False, False, False, False, False, False, False, + False, False, False, False, False, False, False, False, False, False, False) + ) + bpy.context.object.name = "Cuerda" + # Blender will automatically append the .001 + # if it is already in data + real_name = bpy.context.object.name + + for i in range(len(cuboslink)): + cubonombre = cuboslink[i].name + seleccionar_por_nombre(cubonombre) + seleccionar_por_nombre(real_name) + x = cuboslink[i].location[0] + y = cuboslink[i].location[1] + z = cuboslink[i].location[2] + + # if it is 0 make it start from 1 as the offset from the ground... + if i == 0: + i = offset_del_suelo + else: # if it is not 0, add one so it doesn't step on the first one starting from 1 + i = i + offset_del_suelo + + salir_de_editmode() + entrar_en_editmode() + + if i == 1: + # select all the vertices and delete them + select_all_vertex_in_curve_bezier(bpy.data.objects[real_name]) + bpy.ops.curve.delete(type='VERT') + # create the first vertex: + bpy.ops.curve.vertex_add(location=(x, y, z)) + else: + # extrude the rest: + bpy.ops.curve.extrude_move( + CURVE_OT_extrude={"mode": 'TRANSLATION'}, + TRANSFORM_OT_translate={ + "value": (0, 0, z / i), + "constraint_axis": (False, False, True), + "constraint_orientation": 'GLOBAL', "mirror": False, + "proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH', + "proportional_size": 1, "snap": False, "snap_target": 'CLOSEST', + "snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0), + "gpencil_strokes": False, "texture_space": False, + "remove_on_cancel": False, "release_confirm": False + } + ) + bpy.ops.object.hook_add_selob(use_bone=False) + salir_de_editmode() + bpy.context.object.data.bevel_resolution = resolucion + deseleccionar_todo() + + # create a sphere ball: + deseleccionar_todo() + seleccionar_por_nombre(cuboslink[0].name) + entrar_en_editmode() + z = cuboslink[0].scale.z + longitud / 2 + bpy.ops.view3d.snap_cursor_to_selected() + bpy.ops.mesh.primitive_uv_sphere_add( + view_align=False, enter_editmode=False, + layers=(True, False, False, False, False, False, False, + False, False, False, False, False, False, False, + False, False, False, False, False, False) + ) + bpy.ops.transform.translate( + value=(0, 0, -z + 2), constraint_axis=(False, False, True), + constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', proportional_size=1 + ) + bpy.ops.transform.resize( + value=(longitud / 2, longitud / 2, longitud / 2), + constraint_axis=(False, False, False), + constraint_orientation='GLOBAL', + mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', proportional_size=1 + ) + deselect_all_in_edit_mode(cuboslink[0]) + salir_de_editmode() + bpy.ops.object.shade_smooth() + bpy.context.object.rigid_body.mass = masa + bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS') + + # move it all up a bit more: + seleccionar_todo() + deseleccionar_por_nombre(groundplane_name) + bpy.ops.transform.translate( + value=(0, 0, offset_del_suelo_real), + constraint_axis=(False, False, True), + constraint_orientation='GLOBAL', mirror=False, + proportional='DISABLED', proportional_edit_falloff='SMOOTH', + proportional_size=1 + ) + + deseleccionar_todo() + seleccionar_por_nombre(cuboslink[-1].name) + bpy.ops.rigidbody.objects_add(type='PASSIVE') + + bpy.context.scene.rigidbody_world.steps_per_second = world_steps + bpy.context.scene.rigidbody_world.solver_iterations = solver_iterations + + # move everything from the top one: + seleccionar_por_nombre(cuboslink[-1].name) + bpy.ops.view3d.snap_cursor_to_selected() + seleccionar_todo() + deseleccionar_por_nombre(groundplane_name) + deseleccionar_por_nombre(cuboslink[-1].name) + bpy.context.space_data.pivot_point = 'CURSOR' + bpy.ops.transform.rotate( + value=rotrope, axis=(1, 0, 0), + constraint_axis=(True, False, False), + constraint_orientation='GLOBAL', + mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', + proportional_size=1 + ) + bpy.context.space_data.pivot_point = 'MEDIAN_POINT' + deseleccionar_todo() + + seleccionar_por_nombre(real_name) + bpy.context.object.data.fill_mode = 'FULL' + bpy.context.object.data.bevel_depth = radiorope + for ob in bpy.data.objects: + if ob.name != cuboslink[0].name: + if ob.name.find("CubeLink") >= 0: + deseleccionar_todo() + seleccionar_por_nombre(ob.name) + if hidecubeslinks: + bpy.context.object.hide = True + ocultar_relationships() + deseleccionar_todo() + return {'FINISHED'} + + def invoke(self, context, event): + return context.window_manager.invoke_props_dialog(self, width=350) + + def draw(self, context): + layout = self.layout + box = layout.box() + col = box.column(align=True) + + col.label("Rope settings:") + rowsub0 = col.row() + rowsub0.prop(self, "hidecubes", text="Hide Link Cubes") + + rowsub1 = col.row(align=True) + rowsub1.prop(self, "ropelenght2", text="Length") + rowsub1.prop(self, "ropesegments2", text="Segments") + + rowsub2 = col.row(align=True) + rowsub2.prop(self, "radiuscubes", text="Radius Link Cubes") + rowsub2.prop(self, "radiusrope", text="Radius Rope") + + rowsub3 = col.row(align=True) + rowsub3.prop(self, "grados", text="Degrees") + rowsub3.prop(self, "separacion", text="Separation Link Cubes") + + col.label("Quality Settings:") + col.prop(self, "resrope", text="Resolution Rope") + col.prop(self, "massball", text="Ball Mass") + col.prop(self, "worldsteps", text="World Steps") + col.prop(self, "solveriterations", text="Solver Iterarions") + + +# Register + +def register(): + bpy.utils.register_module(__name__) + + +def unregister(): + bpy.utils.unregister_module(__name__) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects_menu/scene_objects_bi.py b/add_advanced_objects_menu/scene_objects_bi.py new file mode 100644 index 00000000..f189bb11 --- /dev/null +++ b/add_advanced_objects_menu/scene_objects_bi.py @@ -0,0 +1,195 @@ +# gpl: author meta-androcto + +import bpy +from bpy.types import Operator + + +class add_BI_scene(Operator): + bl_idname = "bi.add_scene" + bl_label = "Create test scene" + bl_description = "Blender Internal renderer Scene with Objects" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + try: + blend_data = context.blend_data + # ob = bpy.context.active_object + + # add new scene + bpy.ops.scene.new(type="NEW") + scene = bpy.context.scene + scene.name = "scene_materials" + + # render settings + render = scene.render + render.resolution_x = 1920 + render.resolution_y = 1080 + render.resolution_percentage = 50 + + # add new world + world = bpy.data.worlds.new("Materials_World") + scene.world = world + world.use_sky_blend = True + world.use_sky_paper = True + world.horizon_color = (0.004393, 0.02121, 0.050) + world.zenith_color = (0.03335, 0.227, 0.359) + world.light_settings.use_ambient_occlusion = True + world.light_settings.ao_factor = 0.25 + + # add camera + bpy.ops.object.camera_add( + location=(7.48113, -6.50764, 5.34367), + rotation=(1.109319, 0.010817, 0.814928) + ) + cam = bpy.context.active_object.data + cam.lens = 35 + cam.draw_size = 0.1 + bpy.ops.view3d.viewnumpad(type='CAMERA') + + # add point lamp + bpy.ops.object.lamp_add( + type="POINT", location=(4.07625, 1.00545, 5.90386), + rotation=(0.650328, 0.055217, 1.866391) + ) + lamp1 = bpy.context.active_object.data + lamp1.name = "Point_Right" + lamp1.energy = 1.0 + lamp1.distance = 30.0 + lamp1.shadow_method = "RAY_SHADOW" + lamp1.use_sphere = True + + # add point lamp2 + bpy.ops.object.lamp_add( + type="POINT", location=(-0.57101, -4.24586, 5.53674), + rotation=(1.571, 0, 0.785) + ) + lamp2 = bpy.context.active_object.data + lamp2.name = "Point_Left" + lamp2.energy = 1.0 + lamp2.distance = 30.0 + + # Add cube + bpy.ops.mesh.primitive_cube_add() + bpy.ops.object.editmode_toggle() + bpy.ops.mesh.subdivide(number_cuts=2) + bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001) + bpy.ops.object.editmode_toggle() + + cube = bpy.context.active_object + # add new material + cubeMaterial = blend_data.materials.new("Cube_Material") + bpy.ops.object.material_slot_add() + cube.material_slots[0].material = cubeMaterial + # Diffuse + cubeMaterial.preview_render_type = "CUBE" + cubeMaterial.diffuse_color = (1.000, 0.373, 0.00) + cubeMaterial.diffuse_shader = 'OREN_NAYAR' + cubeMaterial.diffuse_intensity = 1.0 + cubeMaterial.roughness = 0.09002 + # Specular + cubeMaterial.specular_color = (1.000, 0.800, 0.136) + cubeMaterial.specular_shader = "PHONG" + cubeMaterial.specular_intensity = 1.0 + cubeMaterial.specular_hardness = 511.0 + # Shading + cubeMaterial.ambient = 1.00 + cubeMaterial.use_cubic = False + # Transparency + cubeMaterial.use_transparency = False + cubeMaterial.alpha = 0 + # Mirror + cubeMaterial.raytrace_mirror.use = True + cubeMaterial.mirror_color = (1.000, 0.793, 0.0) + cubeMaterial.raytrace_mirror.reflect_factor = 0.394 + cubeMaterial.raytrace_mirror.fresnel = 2.0 + cubeMaterial.raytrace_mirror.fresnel_factor = 1.641 + cubeMaterial.raytrace_mirror.fade_to = "FADE_TO_SKY" + cubeMaterial.raytrace_mirror.gloss_anisotropic = 1.0 + # Shadow + cubeMaterial.use_transparent_shadows = True + + # Add a texture + cubetex = blend_data.textures.new("CloudTex", type='CLOUDS') + cubetex.noise_type = 'SOFT_NOISE' + cubetex.noise_scale = 0.25 + mtex = cubeMaterial.texture_slots.add() + mtex.texture = cubetex + mtex.texture_coords = 'ORCO' + mtex.scale = (0.800, 0.800, 0.800) + mtex.use_map_mirror = True + mtex.mirror_factor = 0.156 + mtex.use_map_color_diffuse = True + mtex.diffuse_color_factor = 0.156 + mtex.use_map_normal = True + mtex.normal_factor = 0.010 + mtex.blend_type = "ADD" + mtex.use_rgb_to_intensity = True + mtex.color = (1.000, 0.207, 0.000) + + # Add monkey + bpy.ops.mesh.primitive_monkey_add(location=(-0.1, 0.08901, 1.505)) + bpy.ops.transform.rotate(value=(1.15019), axis=(0, 0, 1)) + bpy.ops.transform.rotate(value=(-0.673882), axis=(0, 1, 0)) + bpy.ops.transform.rotate(value=-0.055, axis=(1, 0, 0)) + bpy.ops.object.modifier_add(type='SUBSURF') + bpy.ops.object.shade_smooth() + monkey = bpy.context.active_object + # add new material + monkeyMaterial = blend_data.materials.new("Monkey_Material") + bpy.ops.object.material_slot_add() + monkey.material_slots[0].material = monkeyMaterial + # Material settings + monkeyMaterial.preview_render_type = "MONKEY" + monkeyMaterial.diffuse_color = (0.239, 0.288, 0.288) + monkeyMaterial.specular_color = (0.604, 0.465, 0.136) + monkeyMaterial.diffuse_shader = 'LAMBERT' + monkeyMaterial.diffuse_intensity = 1.0 + monkeyMaterial.specular_intensity = 0.3 + monkeyMaterial.ambient = 0 + monkeyMaterial.type = 'SURFACE' + monkeyMaterial.use_cubic = True + monkeyMaterial.use_transparency = False + monkeyMaterial.alpha = 0 + monkeyMaterial.use_transparent_shadows = True + monkeyMaterial.raytrace_mirror.use = True + monkeyMaterial.raytrace_mirror.reflect_factor = 0.65 + monkeyMaterial.raytrace_mirror.fade_to = "FADE_TO_MATERIAL" + + # Add plane + bpy.ops.mesh.primitive_plane_add( + radius=50, view_align=False, enter_editmode=False, location=(0, 0, -1) + ) + bpy.ops.object.editmode_toggle() + bpy.ops.transform.rotate( + value=-0.8, axis=(0, 0, 1), constraint_axis=(False, False, True), + constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', + proportional_edit_falloff='SMOOTH', proportional_size=1 + ) + bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001) + bpy.ops.object.editmode_toggle() + plane = bpy.context.active_object + # add new material + planeMaterial = blend_data.materials.new("Plane_Material") + bpy.ops.object.material_slot_add() + plane.material_slots[0].material = planeMaterial + # Material settings + planeMaterial.preview_render_type = "CUBE" + planeMaterial.diffuse_color = (0.2, 0.2, 0.2) + planeMaterial.specular_color = (0.604, 0.465, 0.136) + planeMaterial.specular_intensity = 0.3 + planeMaterial.ambient = 0 + planeMaterial.use_cubic = True + planeMaterial.use_transparency = False + planeMaterial.alpha = 0 + planeMaterial.use_transparent_shadows = True + + except Exception as e: + self.report({'WARNING'}, + "Some operations could not be performed (See Console for more info)") + + print("\n[Add Advanced Objects]\nOperator: " + "bi.add_scene\nError: {}".format(e)) + + return {'CANCELLED'} + + return {"FINISHED"} diff --git a/add_advanced_objects_menu/scene_objects_cycles.py b/add_advanced_objects_menu/scene_objects_cycles.py new file mode 100644 index 00000000..85e85867 --- /dev/null +++ b/add_advanced_objects_menu/scene_objects_cycles.py @@ -0,0 +1,142 @@ +# gpl: author meta-androcto + +import bpy +from bpy.types import Operator + + +class add_cycles_scene(Operator): + bl_idname = "objects_cycles.add_scene" + bl_label = "Create test scene" + bl_description = "Cycles renderer Scene with Objects" + bl_options = {'REGISTER'} + + def execute(self, context): + try: + blend_data = context.blend_data + + # add new scene + bpy.ops.scene.new(type="NEW") + scene = bpy.context.scene + bpy.context.scene.render.engine = 'CYCLES' + scene.name = "scene_object_cycles" + + # render settings + render = scene.render + render.resolution_x = 1920 + render.resolution_y = 1080 + render.resolution_percentage = 50 + + # add new world + world = bpy.data.worlds.new("Cycles_Object_World") + scene.world = world + world.use_sky_blend = True + world.use_sky_paper = True + world.horizon_color = (0.004393, 0.02121, 0.050) + world.zenith_color = (0.03335, 0.227, 0.359) + world.light_settings.use_ambient_occlusion = True + world.light_settings.ao_factor = 0.25 + + # add camera + bpy.ops.object.camera_add( + location=(7.48113, -6.50764, 5.34367), + rotation=(1.109319, 0.010817, 0.814928) + ) + cam = bpy.context.active_object.data + cam.lens = 35 + cam.draw_size = 0.1 + bpy.ops.view3d.viewnumpad(type='CAMERA') + + # add point lamp + bpy.ops.object.lamp_add( + type="POINT", location=(4.07625, 1.00545, 5.90386), + rotation=(0.650328, 0.055217, 1.866391) + ) + lamp1 = bpy.context.active_object.data + lamp1.name = "Point_Right" + lamp1.energy = 1.0 + lamp1.distance = 30.0 + lamp1.shadow_method = "RAY_SHADOW" + lamp1.use_sphere = True + + # add point lamp2 + bpy.ops.object.lamp_add( + type="POINT", location=(-0.57101, -4.24586, 5.53674), + rotation=(1.571, 0, 0.785) + ) + lamp2 = bpy.context.active_object.data + lamp2.name = "Point_Left" + lamp2.energy = 1.0 + lamp2.distance = 30.0 + + # Add cube + bpy.ops.mesh.primitive_cube_add() + bpy.ops.object.editmode_toggle() + bpy.ops.mesh.subdivide(number_cuts=2) + bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001) + bpy.ops.object.editmode_toggle() + cube = bpy.context.active_object + + # add cube material + cubeMaterial = blend_data.materials.new("Cycles_Cube_Material") + bpy.ops.object.material_slot_add() + cube.material_slots[0].material = cubeMaterial + # Diffuse + cubeMaterial.preview_render_type = "CUBE" + cubeMaterial.diffuse_color = (1.000, 0.373, 0.00) + # Cycles + cubeMaterial.use_nodes = True + + # Add monkey + bpy.ops.mesh.primitive_monkey_add(location=(-0.1, 0.08901, 1.505)) + bpy.ops.transform.rotate(value=(1.15019), axis=(0, 0, 1)) + bpy.ops.transform.rotate(value=(-0.673882), axis=(0, 1, 0)) + bpy.ops.transform.rotate(value=-0.055, axis=(1, 0, 0)) + + bpy.ops.object.modifier_add(type='SUBSURF') + bpy.ops.object.shade_smooth() + monkey = bpy.context.active_object + + # add monkey material + monkeyMaterial = blend_data.materials.new("Cycles_Monkey_Material") + bpy.ops.object.material_slot_add() + monkey.material_slots[0].material = monkeyMaterial + # Diffuse + monkeyMaterial.preview_render_type = "MONKEY" + monkeyMaterial.diffuse_color = (0.239, 0.288, 0.288) + # Cycles + monkeyMaterial.use_nodes = True + + # Add plane + bpy.ops.mesh.primitive_plane_add( + radius=50, view_align=False, + enter_editmode=False, location=(0, 0, -1) + ) + bpy.ops.object.editmode_toggle() + bpy.ops.transform.rotate( + value=-0.8, axis=(0, 0, 1), + constraint_axis=(False, False, True) + ) + bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001) + bpy.ops.object.editmode_toggle() + plane = bpy.context.active_object + + # add plane material + planeMaterial = blend_data.materials.new("Cycles_Plane_Material") + bpy.ops.object.material_slot_add() + plane.material_slots[0].material = planeMaterial + # Diffuse + planeMaterial.preview_render_type = "FLAT" + planeMaterial.diffuse_color = (0.2, 0.2, 0.2) + # Cycles + planeMaterial.use_nodes = True + + except Exception as e: + self.report({'WARNING'}, + "Some operations could not be performed (See Console for more info)") + + print("\n[Add Advanced Objects]\nOperator: " + "objects_cycles.add_scene\nError: {}".format(e)) + + return {'CANCELLED'} + + return {'FINISHED'} diff --git a/add_advanced_objects_menu/scene_texture_render.py b/add_advanced_objects_menu/scene_texture_render.py new file mode 100644 index 00000000..02d6490b --- /dev/null +++ b/add_advanced_objects_menu/scene_texture_render.py @@ -0,0 +1,78 @@ +# gpl: author meta-androcto + +import bpy +from bpy.types import Operator + + +class add_texture_scene(Operator): + bl_idname = "objects_texture.add_scene" + bl_label = "Create test scene" + bl_description = "Cycles renderer Scene: Camera aligned to a plane" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + try: + blend_data = context.blend_data + + # add new scene + bpy.ops.scene.new(type="NEW") + scene = bpy.context.scene + bpy.context.scene.render.engine = 'CYCLES' + scene.name = "scene_texture_cycles" + + # render settings + render = scene.render + render.resolution_x = 1080 + render.resolution_y = 1080 + render.resolution_percentage = 100 + + # add new world + world = bpy.data.worlds.new("Cycles_Textures_World") + scene.world = world + world.use_sky_blend = True + world.use_sky_paper = True + world.horizon_color = (0.004393, 0.02121, 0.050) + world.zenith_color = (0.03335, 0.227, 0.359) + world.light_settings.use_ambient_occlusion = True + world.light_settings.ao_factor = 0.5 + + # add camera + bpy.ops.view3d.viewnumpad(type='TOP') + bpy.ops.object.camera_add( + location=(0, 0, 2.1850), rotation=(0, 0, 0), view_align=True + ) + cam = bpy.context.active_object.data + cam.lens = 35 + cam.draw_size = 0.1 + + # add plane + bpy.ops.mesh.primitive_plane_add(enter_editmode=True, location=(0, 0, 0)) + bpy.ops.mesh.subdivide(number_cuts=10, smoothness=0) + bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001) + bpy.ops.object.editmode_toggle() + plane = bpy.context.active_object + + # add plane material + planeMaterial = blend_data.materials.new("Cycles_Plane_Material") + bpy.ops.object.material_slot_add() + plane.material_slots[0].material = planeMaterial + # Diffuse + planeMaterial.preview_render_type = "FLAT" + planeMaterial.diffuse_color = (0.2, 0.2, 0.2) + # Cycles + planeMaterial.use_nodes = True + + # Back to Scene + sc = bpy.context.scene + bpy.ops.view3d.viewnumpad(type='CAMERA') + + except Exception as e: + self.report({'WARNING'}, + "Some operations could not be performed (See Console for more info)") + + print("\n[Add Advanced Objects]\nOperator: " + "objects_texture.add_scene\nError: {}".format(e)) + + return {'CANCELLED'} + + return {'FINISHED'} diff --git a/add_advanced_objects_menu/trilighting.py b/add_advanced_objects_menu/trilighting.py new file mode 100644 index 00000000..e0068e66 --- /dev/null +++ b/add_advanced_objects_menu/trilighting.py @@ -0,0 +1,238 @@ +# gpl: author Daniel Schalla + +import bpy +from bpy.types import Operator +from bpy.props import ( + EnumProperty, + FloatProperty, + IntProperty, + ) +from math import ( + sin, cos, + radians, + sqrt, + ) + + +class TriLighting(Operator): + bl_idname = "object.trilighting" + bl_label = "Tri-Lighting Creator" + bl_description = ("Add 3 Point Lighting to Selected / Active Object\n" + "Needs an existing Active Object") + bl_options = {'REGISTER', 'UNDO'} + + height = FloatProperty( + name="Height", + default=5 + ) + distance = FloatProperty( + name="Distance", + default=5, + min=0.1, + subtype="DISTANCE" + ) + energy = IntProperty( + name="Base Energy", + default=3, + min=1 + ) + contrast = IntProperty( + name="Contrast", + default=50, + min=-100, max=100, + subtype="PERCENTAGE" + ) + leftangle = IntProperty( + name="Left Angle", + default=26, + min=1, max=90, + subtype="ANGLE" + ) + rightangle = IntProperty( + name="Right Angle", + default=45, + min=1, max=90, + subtype="ANGLE" + ) + backangle = IntProperty( + name="Back Angle", + default=235, + min=90, max=270, + subtype="ANGLE" + ) + Light_Type_List = [ + ('POINT', "Point", "Point Light"), + ('SUN', "Sun", "Sun Light"), + ('SPOT', "Spot", "Spot Light"), + ('HEMI', "Hemi", "Hemi Light"), + ('AREA', "Area", "Area Light") + ] + primarytype = EnumProperty( + attr='tl_type', + name="Key Type", + description="Choose the types of Key Lights you would like", + items=Light_Type_List, + default='HEMI' + ) + secondarytype = EnumProperty( + attr='tl_type', + name="Fill + Back Type", + description="Choose the types of secondary Lights you would like", + items=Light_Type_List, + default="POINT" + ) + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def draw(self, context): + layout = self.layout + + layout.label("Position:") + col = layout.column(align=True) + col.prop(self, "height") + col.prop(self, "distance") + + layout.label("Light:") + col = layout.column(align=True) + col.prop(self, "energy") + col.prop(self, "contrast") + + layout.label("Orientation:") + col = layout.column(align=True) + col.prop(self, "leftangle") + col.prop(self, "rightangle") + col.prop(self, "backangle") + + col = layout.column() + col.label("Key Light Type:") + col.prop(self, "primarytype", text="") + col.label("Fill + Back Type:") + col.prop(self, "secondarytype", text="") + + def execute(self, context): + try: + scene = context.scene + view = context.space_data + if view.type == 'VIEW_3D' and not view.lock_camera_and_layers: + camera = view.camera + else: + camera = scene.camera + + if (camera is None): + cam_data = bpy.data.cameras.new(name='Camera') + cam_obj = bpy.data.objects.new(name='Camera', object_data=cam_data) + scene.objects.link(cam_obj) + scene.camera = cam_obj + bpy.ops.view3d.camera_to_view() + camera = cam_obj + bpy.ops.view3d.viewnumpad(type='TOP') + + obj = bpy.context.scene.objects.active + + # Calculate Energy for each Lamp + if(self.contrast > 0): + keyEnergy = self.energy + backEnergy = (self.energy / 100) * abs(self.contrast) + fillEnergy = (self.energy / 100) * abs(self.contrast) + else: + keyEnergy = (self.energy / 100) * abs(self.contrast) + backEnergy = self.energy + fillEnergy = self.energy + + # Calculate Direction for each Lamp + + # Calculate current Distance and get Delta + obj_position = obj.location + cam_position = camera.location + + delta_position = cam_position - obj_position + vector_length = sqrt( + (pow(delta_position.x, 2) + + pow(delta_position.y, 2) + + pow(delta_position.z, 2)) + ) + if not vector_length: + # division by zero most likely + self.report({'WARNING'}, + "Operation Cancelled. No viable object in the scene") + + return {'CANCELLED'} + + single_vector = (1 / vector_length) * delta_position + + # Calc back position + singleback_vector = single_vector.copy() + singleback_vector.x = cos(radians(self.backangle)) * single_vector.x + \ + (-sin(radians(self.backangle)) * single_vector.y) + + singleback_vector.y = sin(radians(self.backangle)) * single_vector.x + \ + (cos(radians(self.backangle)) * single_vector.y) + + backx = obj_position.x + self.distance * singleback_vector.x + backy = obj_position.y + self.distance * singleback_vector.y + + backData = bpy.data.lamps.new(name="TriLamp-Back", type=self.secondarytype) + backData.energy = backEnergy + + backLamp = bpy.data.objects.new(name="TriLamp-Back", object_data=backData) + scene.objects.link(backLamp) + backLamp.location = (backx, backy, self.height) + + trackToBack = backLamp.constraints.new(type="TRACK_TO") + trackToBack.target = obj + trackToBack.track_axis = "TRACK_NEGATIVE_Z" + trackToBack.up_axis = "UP_Y" + + # Calc right position + singleright_vector = single_vector.copy() + singleright_vector.x = cos(radians(self.rightangle)) * single_vector.x + \ + (-sin(radians(self.rightangle)) * single_vector.y) + + singleright_vector.y = sin(radians(self.rightangle)) * single_vector.x + \ + (cos(radians(self.rightangle)) * single_vector.y) + + rightx = obj_position.x + self.distance * singleright_vector.x + righty = obj_position.y + self.distance * singleright_vector.y + + rightData = bpy.data.lamps.new(name="TriLamp-Fill", type=self.secondarytype) + rightData.energy = fillEnergy + rightLamp = bpy.data.objects.new(name="TriLamp-Fill", object_data=rightData) + scene.objects.link(rightLamp) + rightLamp.location = (rightx, righty, self.height) + trackToRight = rightLamp.constraints.new(type="TRACK_TO") + trackToRight.target = obj + trackToRight.track_axis = "TRACK_NEGATIVE_Z" + trackToRight.up_axis = "UP_Y" + + # Calc left position + singleleft_vector = single_vector.copy() + singleleft_vector.x = cos(radians(-self.leftangle)) * single_vector.x + \ + (-sin(radians(-self.leftangle)) * single_vector.y) + singleleft_vector.y = sin(radians(-self.leftangle)) * single_vector.x + \ + (cos(radians(-self.leftangle)) * single_vector.y) + leftx = obj_position.x + self.distance * singleleft_vector.x + lefty = obj_position.y + self.distance * singleleft_vector.y + + leftData = bpy.data.lamps.new(name="TriLamp-Key", type=self.primarytype) + leftData.energy = keyEnergy + + leftLamp = bpy.data.objects.new(name="TriLamp-Key", object_data=leftData) + scene.objects.link(leftLamp) + leftLamp.location = (leftx, lefty, self.height) + trackToLeft = leftLamp.constraints.new(type="TRACK_TO") + trackToLeft.target = obj + trackToLeft.track_axis = "TRACK_NEGATIVE_Z" + trackToLeft.up_axis = "UP_Y" + + except Exception as e: + self.report({'WARNING'}, + "Some operations could not be performed (See Console for more info)") + + print("\n[Add Advanced Objects]\nOperator: " + "object.trilighting\nError: {}".format(e)) + + return {'CANCELLED'} + + return {'FINISHED'} |