diff options
author | meta-androcto <meta.androcto1@gmail.com> | 2017-04-15 06:49:21 +0300 |
---|---|---|
committer | meta-androcto <meta.androcto1@gmail.com> | 2017-04-15 06:49:21 +0300 |
commit | bd42467c77f46801318883fb63b75de2f5ae6fac (patch) | |
tree | 733760808a5021642cce6dbbd6bea6ed3bbf4f35 /add_advanced_objects | |
parent | 5f883054ce5904d4d1fb34216530b6df9cb04a81 (diff) |
Initial commit add advanced objects: T51110 T50357
Diffstat (limited to 'add_advanced_objects')
26 files changed, 9324 insertions, 0 deletions
diff --git a/add_advanced_objects/__init__.py b/add_advanced_objects/__init__.py new file mode 100644 index 00000000..3c15d1b4 --- /dev/null +++ b/add_advanced_objects/__init__.py @@ -0,0 +1,252 @@ +# ##### 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 # +# Dannyboy, Mano-Wii, Kursad Karatas, teldredge + +bl_info = { + "name": "Add Advanced Objects", + "author": "Meta Androcto,", + "version": (0, 1, 1), + "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", + "tracker_url": "", + "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(drop_to_ground) + importlib.reload(circle_array) + importlib.reload(unfold_transition) + 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(object_mangle_tools) + importlib.reload(arrange_on_curve) + importlib.reload(object_laplace_lightning) + importlib.reload(mesh_easylattice) + importlib.reload(DelaunayVoronoi) + importlib.reload(delaunayVoronoiBlender) + importlib.reload(oscurart_constellation) + importlib.reload(oscurart_chain_maker) + +else: + from . import ( + add_light_template, + scene_objects_bi, + scene_objects_cycles, + scene_texture_render, + trilighting, + pixelate_3d, + object_add_chain, + oscurart_chain_maker, + drop_to_ground, + circle_array, + unfold_transition, + copy2, + make_struts, + random_box_structure, + cubester, + rope_alpha, + add_mesh_aggregate, + object_mangle_tools, + arrange_on_curve, + object_laplace_lightning, + mesh_easylattice + ) + from .delaunay_voronoi import ( + DelaunayVoronoi, + delaunayVoronoiBlender, + oscurart_constellation + ) + +import bpy +from bpy.types import ( + Menu, + AddonPreferences, + ) + + +class INFO_MT_scene_elements_add(Menu): + # Define the "scenes" 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") + + +class INFO_MT_mesh_lamps_add(Menu): + # Define the "lights" 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") + + +class INFO_MT_mesh_chain_add(Menu): + # Define the "Chains" 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") + + +class INFO_MT_array_mods_add(Menu): + # Define the "array" menu + bl_idname = "INFO_MT_array_mods" + bl_label = "Array Mods" + + def draw(self, context): + layout = self.layout + layout.operator_context = 'INVOKE_REGION_WIN' + self.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') + obj = context.object + if obj.type in ['MESH',]: + layout.operator("mesh.copy2", + text="Copy To Vert/Edge", icon='MOD_ARRAY') + + + +class INFO_MT_quick_blocks_add(Menu): + # Define the "Blocks" 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') + obj = context.object + if obj.type in ['MESH',]: + layout.operator("mesh.generate_struts", + text="Struts", icon='GRID') + layout.operator("object.easy_lattice", + text="Easy Lattice", icon='MOD_LATTICE') + layout.operator("object.make_structure", + text="Random Boxes", icon='SEQ_SEQUENCER') + + +class INFO_MT_Physics_tools_add(Menu): + # Define the "mesh objects" 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") + 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 AddonPreferences(AddonPreferences): + bl_idname = __name__ + + def draw(self, context): + layout = self.layout + layout.label(text="----Add Menu Advanced----") + layout.label(text="Quick Tools:") + layout.label(text="Drop, Pixelate & Wrecking Ball") + layout.label(text="Array Mods:") + layout.label(text="Circle Array, Chains, Vert to Edge, Aggregate") + + +def register(): + object_mangle_tools.register() + arrange_on_curve.register() + bpy.utils.register_module(__name__) + # 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(): + object_mangle_tools.unregister() + arrange_on_curve.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__) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/add_light_template.py b/add_advanced_objects/add_light_template.py new file mode 100644 index 00000000..312b2d9e --- /dev/null +++ b/add_advanced_objects/add_light_template.py @@ -0,0 +1,136 @@ +# gpl: author Rebellion + +import bpy +from bpy.types import Operator +from bpy.props import BoolProperty + + +def add_lamps(self, context): + try: + 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: + constraint = self.camera.constraints.new(type='TRACK_TO') + constraint.target = self.target + constraint.track_axis = 'TRACK_NEGATIVE_Z' + constraint.up_axis = 'UP_Y' + + except Exception as e: + self.report({'WARNING'}, + "Some operations could not be performed (See Console for more info)") + + print("\n[object.add_light_template]\nError: {}".format(e)) + + +class OBJECT_OT_add_light_template(Operator): + bl_idname = "object.add_light_template" + bl_label = "Add Light Template" + bl_description = "Add Key, Fill & Back Lights" + bl_options = {'REGISTER', 'UNDO'} + + camera = None + target = None + + bKeyLight = BoolProperty( + name="Key Light", + default=True + ) + bFillLight = BoolProperty( + name="Fill Light", + default=True + ) + bBackLight = BoolProperty( + name="Back Light", + default=True + ) + camera_constraint = BoolProperty( + name="Camera Constraint", + default=False + ) + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def execute(self, context): + 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) + + 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/add_mesh_aggregate.py b/add_advanced_objects/add_mesh_aggregate.py new file mode 100644 index 00000000..c8ec0ac5 --- /dev/null +++ b/add_advanced_objects/add_mesh_aggregate.py @@ -0,0 +1,318 @@ +# ##### 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'} + + updateMeNow = BoolProperty( + name="Update", + description="Update", + default=True + ) + 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" + ) + + def draw(self, context): + layout = self.layout + col = layout.column(align=True) + col.prop(self, "updateMeNow", toggle=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 = layout.column(align=True) + col.prop(self, "nor") + col.prop(self, "cent") + col.prop(self, "track") + col.prop(self, "anim") + + col.prop(self, 'numP') + + @classmethod + def poll(cls, context): + return(len(bpy.context.selected_objects) > 1 and bpy.context.object.type == 'MESH') + + def invoke(self, context, event): + self.updateMeNow = True + return self.execute(context) + + def execute(self, context): + if not self.updateMeNow: + 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'}, "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) + + newobj.data.user_clear() + bpy.data.meshes.remove(newobj.data) + + 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 + + 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/arrange_on_curve.py b/add_advanced_objects/arrange_on_curve.py new file mode 100644 index 00000000..9894de12 --- /dev/null +++ b/add_advanced_objects/arrange_on_curve.py @@ -0,0 +1,362 @@ +# 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": "", + "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", + "category": "3D View" + } + +import bpy +import mathutils +from bpy.types import ( + Operator, + Panel, + ) +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + IntProperty, + StringProperty, + ) + +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 + layout.prop(context.scene, "use_selected") + if not context.scene.use_selected: + layout.prop(context.scene, "select_type", expand=True) + if context.scene.select_type == 'O': + layout.column(align=True).prop_search( + context.scene, "objeto_arranjar", + bpy.data, "objects" + ) + elif context.scene.select_type == 'G': + layout.column(align=True).prop_search( + context.scene, "objeto_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" + bl_options = {'REGISTER', 'UNDO'} + + use_distance = EnumProperty( + 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="Distancia entre objetos", + 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( + default=0.0, + name="X", + unit='ROTATION' + ) + Pitch = FloatProperty( + default=0.0, + name="Y", + unit='ROTATION' + ) + Roll = FloatProperty( + default=0.0, + 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(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) + + if context.scene.use_selected: + G_Objeto = context.selected_objects + G_Objeto.remove(curve) + if not G_Objeto: + return {'CANCELLED'} + elif context.scene.select_type == 'O': + G_Objeto = bpy.data.objects[context.scene.objeto_arranjar], + elif context.scene.select_type == 'G': + G_Objeto = bpy.data.groups[context.scene.objeto_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 # is this used? + + 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) + bpy.types.Scene.use_selected = BoolProperty( + name='Use Selected', + description='Use the selected objects to duplicate', + default=True, + ) + bpy.types.Scene.objeto_arranjar = StringProperty( + name="" + ) + bpy.types.Scene.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 unregister(): + bpy.utils.unregister_class(PanelDupliCurve) + bpy.utils.unregister_class(DupliCurve) + del bpy.types.Scene.objeto_arranjar + del bpy.types.Scene.use_selected + del bpy.types.Scene.select_type + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/circle_array.py b/add_advanced_objects/circle_array.py new file mode 100644 index 00000000..170ea7a6 --- /dev/null +++ b/add_advanced_objects/circle_array.py @@ -0,0 +1,143 @@ +# gpl author: Antonis Karvelas + +# -*- coding: utf-8 -*- + +bl_info = { + "name": "Circle Array", + "author": "Antonis Karvelas", + "version": (1, 0), + "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": "", + "tracker_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 & Surface") + + @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): + try: + allowed_obj = ['MESH', 'CURVE', 'SURFACE', 'FONT'] + if context.active_object.type not in allowed_obj: + self.report( + {"WARNING"}, + "Operation Cancelled. The active object is not of " + "Mesh, Curve, Surface or Font type" + ) + return {'CANCELLED'} + + default_name = self.check_empty_name(context) or "EMPTY_C_Array" + bpy.ops.object.modifier_add(type='ARRAY') + + if len(bpy.context.selected_objects) == 2: + list = bpy.context.selected_objects + active = list[0] + active.modifiers[0].use_object_offset = True + active.modifiers[0].use_relative_offset = False + active.select = False + bpy.context.scene.objects.active = list[0] + 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 circle_array_menu(self, context): + self.layout.operator(Circle_Array.bl_idname, text="Circle_Array") + + +def register(): + bpy.utils.register_class(Circle_Array) + bpy.types.VIEW3D_MT_object.append(circle_array_menu) + + +def unregister(): + bpy.utils.unregister_class(Circle_Array) + bpy.types.VIEW3D_MT_object.remove(circle_array_menu) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/copy2.py b/add_advanced_objects/copy2.py new file mode 100644 index 00000000..3afdef22 --- /dev/null +++ b/add_advanced_objects/copy2.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 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), + "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 mathutils import Vector, Matrix + + +class Copy2(bpy.types.Operator): + bl_idname = "mesh.copy2" + bl_label = "Copy 2" + 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', 'Y'), ('Z', 'Z', 'Z')] + + if self.priaxes == 'Y': + sec_list = [('X', 'X', 'X'), ('Z', 'Z', 'Z')] + + if self.priaxes == 'Z': + sec_list = [('X', 'X', 'X'), ('Y', 'Y', 'Y')] + return sec_list + + copytype = bpy.props.EnumProperty(items=(('V', '', 'paste to vertices', 'VERTEXSEL', 0), + ('E', '', 'paste to edges', 'EDGESEL', 1), + ('F', '', 'paste to faces', 'FACESEL', 2)), + description='where to paste to') + + copyfromobject = bpy.props.EnumProperty(items=obj_list_cb, name='Copy from:') + + priaxes = bpy.props.EnumProperty(items=(('X', 'X', 'along X'), + ('Y', 'Y', 'along Y'), + ('Z', 'Z', 'along Z')), + ) + + edgescale = bpy.props.BoolProperty(name='Scale to fill edge?') + + secaxes = bpy.props.EnumProperty(items=sec_axes_list_cb, name='Secondary Axis') + + scale = bpy.props.FloatProperty(default=1.0, min=0.0, name='Scale') + + 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 + copy_list = copy_to_from(context.scene, + bpy.data.objects[copytoobject], + bpy.data.objects[self.copyfromobject], + self.copytype, + axes, + self.edgescale, + self.scale) + return {"FINISHED"} + + def invoke(self, context, event): + Copy2.obj_list = [(obj.name, obj.name, obj.name) for obj in bpy.data.objects] + return {"FINISHED"} +# end Copy2 class + +#--------------------------------------------------------------------------------------- + + +def add_to_menu(self, context): + self.layout.operator(Copy2.bl_idname) + return + + +#----------------------------------------------------------------- + +def copy_to_from(scene, to_obj, from_obj, copymode, axes, edgescale, scale): + if copymode == 'V': + copy_list = vertex_copy(scene, to_obj, from_obj, axes) + if copymode == 'E': + copy_list = edge_copy(scene, to_obj, from_obj, axes, edgescale, scale) + if copymode == 'F': + copy_list = face_copy(scene, to_obj, from_obj, axes) + return copy_list + +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 != 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 == 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 + return copy_list + + +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 == 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 + return copy_list + + +def face_copy(scene, obj, source_obj, axes): + # face select mode + sel_faces = [] + copy_list = [] + for f in obj.data.polygons: + if f.select == 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 + return copy_list + +#------------------------------------------------------------------- + + +def register(): + + bpy.utils.register_module(__name__) + bpy.types.VIEW3D_MT_object.append(add_to_menu) + + +def unregister(): + + bpy.types.VIEW3D_MT_object.remove(add_to_menu) + bpy.utils.unregister_module(__name__) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/cubester.py b/add_advanced_objects/cubester.py new file mode 100644 index 00000000..83053ad1 --- /dev/null +++ b/add_advanced_objects/cubester.py @@ -0,0 +1,1133 @@ +# ##### 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 + +bl_info = { + "name": "CubeSter", + "author": "Jacob Morris", + "version": (0, 7), + "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.props import ( + BoolProperty, + IntProperty, + FloatProperty, + StringProperty, + EnumProperty, + ) +from bpy.types import ( + Operator, + Panel, + ) + +import timeit +from random import uniform +from math import radians +from os import ( + path, + listdir, + ) + + +# load image if possible +def adjust_selected_image(self, context): + scene = context.scene + try: + image = bpy.data.images.load(scene.cubester_load_image) + scene.cubester_image = image.name + except RuntimeError: + self.report({"ERROR"}, "CubeSter: '{}' could not be loaded".format(scene.cubester_load_image)) + + +# load color image if possible +def adjust_selected_color_image(self, context): + scene = context.scene + try: + image = bpy.data.images.load(scene.cubester_load_color_image) + scene.cubester_color_image = image.name + except RuntimeError: + self.report({"ERROR"}, "CubeSter: '{}' could not be loaded".format(scene.cubester_load_color_image)) + + +# crate 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.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) + + # image + if not scene.cubester_use_image_color and scene.cubester_color_image in bpy.data.images: + image = bpy.data.images[scene.cubester_color_image] + else: + image = bpy.data.images[scene.cubester_image] + + 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") + att.image = image + + if scene.cubester_load_type == "multiple": + att.image.source = "SEQUENCE" + att.location = (-200, 700) + + att = nodes.new("ShaderNodeTexCoord") + att.location = (-450, 600) + + if scene.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 scene.cubester_materials == "image" or scene.render.engine != "BLENDER_RENDER": + tex = bpy.data.textures.new("CubeSter_" + name, "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): + audio_filepath = scene.cubester_audio_path + width = scene.cubester_audio_width_blocks + length = scene.cubester_audio_length_blocks + size_per_hundred = scene.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 scene.cubester_materials == "image": + picture = bpy.data.images[scene.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 + for c in ob.data.vertex_colors[0].data: + c.color = vert_colors[i] + i += 1 + + # image sequence handling + if scene.cubester_load_type == "multiple": + images = find_sequence_images(self, bpy.context) + + frames_vert_colors = [] + + max_images = scene.cubester_max_images + 1 if \ + len(images[0]) > scene.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, scene.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) + + scene.cubester_vertex_colors[ob.name] = {"type": "vertex", "frames": frames_vert_colors, + "frame_skip": scene.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 = scene.cubester_audio_max_freq + min_freq = scene.cubester_audio_min_freq + freq_frame = scene.cubester_audio_offset_type + + freq_step = (max_images - min_freq) / length + freq_sub_step = freq_step / width + + frame_step = scene.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 + + bpy.ops.graph.sound_bake(filepath=bpy.path.abspath(audio_filepath), low=l, high=h) + + # 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 scene.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 + picture = bpy.data.images[scene.cubester_image] + pixels = list(picture.pixels) + + x_pixels = picture.size[0] / (scene.cubester_skip_pixels + 1) + y_pixels = picture.size[1] / (scene.cubester_skip_pixels + 1) + + width = x_pixels / 100 * scene.cubester_size_per_hundred_pixels + height = y_pixels / 100 * scene.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 scene.cubester_skip_pixels + 1 + for row in range(0, picture.size[1], scene.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 + scene.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 scene.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 scene.cubester_mesh_style == "plane": + del vert_colors[len(vert_colors) - 4:len(vert_colors)] + + # create faces if plane based and not block based + if scene.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 scene.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 scene.cubester_materials == "vertex": # vertex color + image_name = "Vertex" + elif not scene.cubester_use_image_color and scene.cubester_color_image in bpy.data.images and \ + scene.cubester_materials == "image": # replaced image + image_name = scene.cubester_color_image + else: # normal image + image_name = scene.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 scene.cubester_load_type == "multiple": + images = find_sequence_images(self, context) + frames_vert_colors = [] + + max_images = scene.cubester_max_images + 1 if \ + len(images[0]) > scene.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, scene.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], scene.cubester_skip_pixels + 1): + for column in range(0, picture.size[0] * 4, 4 + scene.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 scene.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 scene.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 scene.cubester_materials == "vertex" or scene.render.engine == "BLENDER_ENGINE": + scene.cubester_vertex_colors[ob.name] = { + "type": "vertex", "frames": frames_vert_colors, + "frame_skip": scene.cubester_frame_step, + "total_images": max_images + } + else: + scene.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) * scene.cubester_frame_step + + # animate mesh + create_f_curves(mesh, frames, scene.cubester_frame_step, scene.cubester_mesh_style) + + +# generate uv map for object +def create_uv_map(context, rows, columns): + 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 context.scene.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) + + +# returns length in frames +def find_audio_length(self, context): + audio_file = context.scene.cubester_audio_path + length = 0 + + if audio_file != "": + # confirm that strip hasn't been loaded yet + for strip in context.scene.sequence_editor.sequences_all: + 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" + + bpy.ops.sequencer.sound_strip_add(filepath=audio_file) + 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: + length = strip.frame_final_duration + + context.scene.cubester_audio_file_length = str(length) + + +# 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): + if a: # if not completely transparent + normalize = 1 + + # channel weighting + if not scene.cubester_advanced: + composed = 0.25 * r + 0.25 * g + 0.25 * b + 0.25 * a + else: + # user defined weighting + if not scene.cubester_random_weights: + composed = scene.cubester_weight_r * r + scene.cubester_weight_g * g + \ + scene.cubester_weight_b * b + scene.cubester_weight_a * a + total = scene.cubester_weight_r + scene.cubester_weight_g + scene.cubester_weight_b + \ + scene.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 scene.cubester_invert: + h = (1 - composed) * scene.cubester_height_scale * normalize + else: + h = composed * scene.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.cubester_image in bpy.data.images: + image = bpy.data.images[scene.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 + + keys = list(scene.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 = scene.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 scene.cubester_vertex_colors[i] + + +# main properties +bpy.types.Scene.cubester_audio_image = EnumProperty( + name="Input Type", + items=(("image", "Image", ""), + ("audio", "Audio", "")) + ) +bpy.types.Scene.cubester_audio_file_length = StringProperty( + default="" + ) +# audio +bpy.types.Scene.cubester_audio_path = StringProperty( + default="", + name="Audio File", + subtype="FILE_PATH", + update=find_audio_length + ) +bpy.types.Scene.cubester_audio_min_freq = IntProperty( + name="Minimum Frequency", + min=20, max=100000, + default=20 + ) +bpy.types.Scene.cubester_audio_max_freq = IntProperty( + name="Maximum Frequency", + min=21, max=999999, + default=5000 + ) +bpy.types.Scene.cubester_audio_offset_type = EnumProperty( + name="Offset Type", + items=(("freq", "Frequency Offset", ""), + ("frame", "Frame Offset", "")), + description="Type of offset per row of mesh" + ) +bpy.types.Scene.cubester_audio_frame_offset = IntProperty( + name="Frame Offset", + min=0, max=10, + default=2 + ) +bpy.types.Scene.cubester_audio_block_layout = EnumProperty( + name="Block Layout", + items=(("rectangle", "Rectangular", ""), + ("radial", "Radial", "")) + ) +bpy.types.Scene.cubester_audio_width_blocks = IntProperty( + name="Width Block Count", + min=1, max=10000, + default=5 + ) +bpy.types.Scene.cubester_audio_length_blocks = IntProperty( + name="Length Block Count", + min=1, max=10000, + default=50 + ) +# image +bpy.types.Scene.cubester_load_type = EnumProperty( + name="Image Input Type", + items=(("single", "Single Image", ""), + ("multiple", "Image Sequence", "")) + ) +bpy.types.Scene.cubester_image = StringProperty( + default="", + name="" + ) +bpy.types.Scene.cubester_load_image = StringProperty( + default="", + name="Load Image", + subtype="FILE_PATH", + update=adjust_selected_image + ) +bpy.types.Scene.cubester_skip_images = IntProperty( + name="Image Step", + min=1, max=30, + default=1, + description="Step from image to image by this number" + ) +bpy.types.Scene.cubester_max_images = IntProperty( + name="Max Number Of Images", + min=2, max=1000, + default=10, + description="Maximum number of images to be used" + ) +bpy.types.Scene.cubester_frame_step = IntProperty( + name="Frame Step Size", + min=1, max=10, + default=4, + description="The number of frames each picture is used" + ) +bpy.types.Scene.cubester_skip_pixels = IntProperty( + name="Skip # Pixels", + min=0, max=256, + default=64, + description="Skip this number of pixels before placing the next" + ) +bpy.types.Scene.cubester_mesh_style = EnumProperty( + name="Mesh Type", + items=(("blocks", "Blocks", ""), + ("plane", "Plane", "")), + description="Compose mesh of multiple blocks or of a single plane" + ) +bpy.types.Scene.cubester_block_style = EnumProperty( + name="Block Style", + items=(("size", "Vary Size", ""), + ("position", "Vary Position", "")), + description="Vary Z-size of block, or vary Z-position" + ) +bpy.types.Scene.cubester_height_scale = FloatProperty( + name="Height Scale", + subtype="DISTANCE", + min=0.1, max=2, + default=0.2 + ) +bpy.types.Scene.cubester_invert = BoolProperty( + name="Invert Height?", + default=False + ) +# general adjustments +bpy.types.Scene.cubester_size_per_hundred_pixels = FloatProperty( + name="Size Per 100 Blocks/Points", + subtype="DISTANCE", + min=0.001, max=5, + default=1 + ) +# material based stuff +bpy.types.Scene.cubester_materials = EnumProperty( + name="Material", + items=(("vertex", "Vertex Colors", ""), + ("image", "Image", "")), + description="Color with vertex colors, or uv unwrap and use an image" + ) +bpy.types.Scene.cubester_use_image_color = BoolProperty( + name="Use Original Image Colors'?", + default=True, + description="Use original image colors, or replace with other" + ) +bpy.types.Scene.cubester_color_image = StringProperty( + default="", name="" + ) +bpy.types.Scene.cubester_load_color_image = StringProperty( + default="", + name="Load Color Image", + subtype="FILE_PATH", + update=adjust_selected_color_image + ) +bpy.types.Scene.cubester_vertex_colors = {} +# advanced +bpy.types.Scene.cubester_advanced = BoolProperty( + name="Advanced Options?" + ) +bpy.types.Scene.cubester_random_weights = BoolProperty( + name="Random Weights?" + ) +bpy.types.Scene.cubester_weight_r = FloatProperty( + name="Red", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) +bpy.types.Scene.cubester_weight_g = FloatProperty( + name="Green", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) +bpy.types.Scene.cubester_weight_b = FloatProperty( + name="Blue", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) +bpy.types.Scene.cubester_weight_a = FloatProperty( + name="Alpha", + subtype="FACTOR", + min=0.01, max=1.0, + default=0.25 + ) + + +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 + images_found = 0 + rows = 0 + columns = 0 + + layout.prop(scene, "cubester_audio_image", icon="IMAGE_COL") + layout.separator() + + if scene.cubester_audio_image == "image": + box = layout.box() + box.prop(scene, "cubester_load_type") + box.label("Image To Convert:") + box.prop_search(scene, "cubester_image", bpy.data, "images") + box.prop(scene, "cubester_load_image") + layout.separator() + + # find number of approriate images if sequence + if scene.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]) <= scene.cubester_max_images \ + else scene.cubester_max_images + + if len(images[0]): + box.label(str(len(images[0])) + " Images Found", icon="PACKAGE") + + box.prop(scene, "cubester_max_images") + box.prop(scene, "cubester_skip_images") + box.prop(scene, "cubester_frame_step") + + layout.separator() + + box = layout.box() + box.prop(scene, "cubester_skip_pixels") + box.prop(scene, "cubester_size_per_hundred_pixels") + box.prop(scene, "cubester_height_scale") + box.prop(scene, "cubester_invert", icon="FILE_REFRESH") + + layout.separator() + box = layout.box() + box.prop(scene, "cubester_mesh_style", icon="MESH_GRID") + + if scene.cubester_mesh_style == "blocks": + box.prop(scene, "cubester_block_style") + + # audio file + else: + layout.prop(scene, "cubester_audio_path") + layout.separator() + box = layout.box() + + box.prop(scene, "cubester_audio_min_freq") + box.prop(scene, "cubester_audio_max_freq") + box.separator() + box.prop(scene, "cubester_audio_offset_type") + + if scene.cubester_audio_offset_type == "frame": + box.prop(scene, "cubester_audio_frame_offset") + box.separator() + + box.prop(scene, "cubester_audio_block_layout") + box.prop(scene, "cubester_audio_width_blocks") + box.prop(scene, "cubester_audio_length_blocks") + + rows = scene.cubester_audio_width_blocks + columns = scene.cubester_audio_length_blocks + + box.prop(scene, "cubester_size_per_hundred_pixels") + + # materials + layout.separator() + box = layout.box() + + box.prop(scene, "cubester_materials", icon="MATERIAL") + + if scene.cubester_materials == "image": + box.prop(scene, "cubester_load_type") + + # find number of approriate images if sequence + if scene.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]) <= scene.cubester_max_images \ + else scene.cubester_max_images + + if len(images[0]): + box.label(str(len(images[0])) + " Images Found", icon="PACKAGE") + box.prop(scene, "cubester_max_images") + box.prop(scene, "cubester_skip_images") + box.prop(scene, "cubester_frame_step") + + box.separator() + + if scene.cubester_audio_image == "image": + box.prop(scene, "cubester_use_image_color", icon="COLOR") + + if not scene.cubester_use_image_color or scene.cubester_audio_image == "audio": + box.label("Image To Use For Colors:") + box.prop_search(scene, "cubester_color_image", bpy.data, "images") + box.prop(scene, "cubester_load_color_image") + + if scene.cubester_image in bpy.data.images: + rows = int(bpy.data.images[scene.cubester_image].size[1] / (scene.cubester_skip_pixels + 1)) + columns = int(bpy.data.images[scene.cubester_image].size[0] / (scene.cubester_skip_pixels + 1)) + + layout.separator() + box = layout.box() + + if scene.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 scene.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 scene.cubester_audio_image == "image": + if scene.cubester_load_type == "single": + time = rows * columns * slope + intercept # approximate time count for mesh + else: + time = (points * slope) + intercept + (points * block_infl) + \ + (images_found / scene.cubester_skip_images * frame_infl) + intercept2 + + box.label("Images To Be Used: " + str(int(images_found / scene.cubester_skip_images))) + + # audio based mesh + else: + box.label("Audio Track Length: " + scene.cubester_audio_file_length + " frames") + + block_infl, frame_infl, intercept = 0.0948, 0.0687566, -25.85985 + time = (points * block_infl) + (int(scene.cubester_audio_file_length) * frame_infl) + intercept + + 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 scene.cubester_audio_image == "image": + layout.separator() + box = layout.box() + box.prop(scene, "cubester_advanced", icon="TRIA_DOWN") + if bpy.context.scene.cubester_advanced: + box.prop(scene, "cubester_random_weights", icon="RNDCURVE") + box.separator() + + if not bpy.context.scene.cubester_random_weights: + box.label("RGBA Channel Weights", icon="COLOR") + box.prop(scene, "cubester_weight_r") + box.prop(scene, "cubester_weight_g") + box.prop(scene, "cubester_weight_b") + box.prop(scene, "cubester_weight_a") + + # generate mesh + layout.separator() + layout.operator("mesh.cubester", icon="OBJECT_DATA") + + +class CubeSter(Operator): + bl_idname = "mesh.cubester" + bl_label = "Generate Mesh" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + verts, faces = [], [] + + start = timeit.default_timer() + scene = bpy.context.scene + + if scene.cubester_audio_image == "image": + create_mesh_from_image(self, scene, verts, faces) + frames = find_sequence_images(self, context) + created = len(frames[0]) + else: + create_mesh_from_audio(self, scene, verts, faces) + created = int(scene.cubester_audio_file_length) + + stop = timeit.default_timer() + + if scene.cubester_mesh_style == "blocks" or scene.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/delaunay_voronoi/DelaunayVoronoi.py b/add_advanced_objects/delaunay_voronoi/DelaunayVoronoi.py new file mode 100644 index 00000000..18d7f38f --- /dev/null +++ b/add_advanced_objects/delaunay_voronoi/DelaunayVoronoi.py @@ -0,0 +1,1000 @@ +# -*- coding: utf-8 -*- + +# Voronoi diagram calculator/ Delaunay triangulator +# +# - Voronoi Diagram Sweepline algorithm and C code by Steven Fortune, +# 1987, http://ect.bell-labs.com/who/sjf/ +# - Python translation to file voronoi.py by Bill Simons, 2005, http://www.oxfish.com/ +# - Additional changes for QGIS by Carson Farmer added November 2010 +# - 2012 Ported to Python 3 and additional clip functions by domlysz at gmail.com +# +# Calculate Delaunay triangulation or the Voronoi polygons for a set of +# 2D input points. +# +# Derived from code bearing the following notice: +# +# The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T +# Bell Laboratories. +# Permission to use, copy, modify, and distribute this software for any +# purpose without fee is hereby granted, provided that this entire notice +# is included in all copies of any software which is or includes a copy +# or modification of this software and in all copies of the supporting +# documentation for such software. +# THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED +# WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY +# REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY +# OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. +# +# Comments were incorporated from Shane O'Sullivan's translation of the +# original code into C++ (http://mapviewer.skynet.ie/voronoi.html) +# +# Steve Fortune's homepage: http://netlib.bell-labs.com/cm/cs/who/sjf/index.html +# +# For programmatic use, two functions are available: +# +# computeVoronoiDiagram(points, xBuff, yBuff, polygonsOutput=False, formatOutput=False): +# Takes : +# - a list of point objects (which must have x and y fields). +# - x and y buffer values which are the expansion percentages of the +# bounding box rectangle including all input points. +# Returns : +# - With default options : +# A list of 2-tuples, representing the two points of each Voronoi diagram edge. +# Each point contains 2-tuples which are the x,y coordinates of point. +# if formatOutput is True, returns : +# - a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices. +# - and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram. +# v1 and v2 are the indices of the vertices at the end of the edge. +# - If polygonsOutput option is True, returns : +# A dictionary of polygons, keys are the indices of the input points, +# values contains n-tuples representing the n points of each Voronoi diagram polygon. +# Each point contains 2-tuples which are the x,y coordinates of point. +# if formatOutput is True, returns : +# - A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices. +# - and a dictionary of input points indices. Values contains n-tuples representing +# the n points of each Voronoi diagram polygon. +# Each tuple contains the vertex indices of the polygon vertices. +# +# computeDelaunayTriangulation(points): +# Takes a list of point objects (which must have x and y fields). +# Returns a list of 3-tuples: the indices of the points that form a Delaunay triangle. + +import bpy +import math +import sys +import getopt +TOLERANCE = 1e-9 +BIG_FLOAT = 1e38 + +# TODO : Licence, prints, imports, autorship + + +class Context(object): + + def __init__(self): + self.doPrint = 0 + self.debug = 0 + + # tuple (xmin, xmax, ymin, ymax) + self.extent = () + self.triangulate = False + # list of vertex 2-tuples: (x,y) + self.vertices = [] + # equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c + self.lines = [] + + # edge 3-tuple: (line index, vertex 1 index, vertex 2 index) + # if either vertex index is -1, the edge extends to infinity + self.edges = [] + # 3-tuple of vertex indices + self.triangles = [] + # a dict of site:[edges] pairs + self.polygons = {} + + +# Clip functions # + def getClipEdges(self): + xmin, xmax, ymin, ymax = self.extent + clipEdges = [] + for edge in self.edges: + equation = self.lines[edge[0]] # line equation + if edge[1] != -1 and edge[2] != -1: # finite line + x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] + x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] + pt1, pt2 = (x1, y1), (x2, y2) + inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2) + if inExtentP1 and inExtentP2: + clipEdges.append((pt1, pt2)) + elif inExtentP1 and not inExtentP2: + pt2 = self.clipLine(x1, y1, equation, leftDir=False) + clipEdges.append((pt1, pt2)) + elif not inExtentP1 and inExtentP2: + pt1 = self.clipLine(x2, y2, equation, leftDir=True) + clipEdges.append((pt1, pt2)) + else: # infinite line + if edge[1] != -1: + x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] + leftDir = False + else: + x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] + leftDir = True + if self.inExtent(x1, y1): + pt1 = (x1, y1) + pt2 = self.clipLine(x1, y1, equation, leftDir) + clipEdges.append((pt1, pt2)) + return clipEdges + + def getClipPolygons(self, closePoly): + xmin, xmax, ymin, ymax = self.extent + poly = {} + for inPtsIdx, edges in self.polygons.items(): + clipEdges = [] + for edge in edges: + equation = self.lines[edge[0]] # line equation + if edge[1] != -1 and edge[2] != -1: # finite line + x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] + x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] + pt1, pt2 = (x1, y1), (x2, y2) + inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2) + if inExtentP1 and inExtentP2: + clipEdges.append((pt1, pt2)) + elif inExtentP1 and not inExtentP2: + pt2 = self.clipLine(x1, y1, equation, leftDir=False) + clipEdges.append((pt1, pt2)) + elif not inExtentP1 and inExtentP2: + pt1 = self.clipLine(x2, y2, equation, leftDir=True) + clipEdges.append((pt1, pt2)) + else: # infinite line + if edge[1] != -1: + x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1] + leftDir = False + else: + x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1] + leftDir = True + if self.inExtent(x1, y1): + pt1 = (x1, y1) + pt2 = self.clipLine(x1, y1, equation, leftDir) + clipEdges.append((pt1, pt2)) + # create polygon definition from edges and check if polygon is completely closed + polyPts, complete = self.orderPts(clipEdges) + if not complete: + startPt = polyPts[0] + endPt = polyPts[-1] + # if start & end points are collinear then they are along an extent border + if startPt[0] == endPt[0] or startPt[1] == endPt[1]: + polyPts.append(polyPts[0]) # simple close + else: # close at extent corner + # upper left + if (startPt[0] == xmin and endPt[1] == ymax) or (endPt[0] == xmin and startPt[1] == ymax): + polyPts.append((xmin, ymax)) # corner point + polyPts.append(polyPts[0]) # close polygon + # upper right + if (startPt[0] == xmax and endPt[1] == ymax) or (endPt[0] == xmax and startPt[1] == ymax): + polyPts.append((xmax, ymax)) + polyPts.append(polyPts[0]) + # bottom right + if (startPt[0] == xmax and endPt[1] == ymin) or (endPt[0] == xmax and startPt[1] == ymin): + polyPts.append((xmax, ymin)) + polyPts.append(polyPts[0]) + # bottom left + if (startPt[0] == xmin and endPt[1] == ymin) or (endPt[0] == xmin and startPt[1] == ymin): + polyPts.append((xmin, ymin)) + polyPts.append(polyPts[0]) + if not closePoly: # unclose polygon + polyPts = polyPts[:-1] + poly[inPtsIdx] = polyPts + return poly + + def clipLine(self, x1, y1, equation, leftDir): + xmin, xmax, ymin, ymax = self.extent + a, b, c = equation + if b == 0: # vertical line + if leftDir: # left is bottom of vertical line + return (x1, ymax) + else: + return (x1, ymin) + elif a == 0: # horizontal line + if leftDir: + return (xmin, y1) + else: + return (xmax, y1) + else: + y2_at_xmin = (c - a * xmin) / b + y2_at_xmax = (c - a * xmax) / b + x2_at_ymin = (c - b * ymin) / a + x2_at_ymax = (c - b * ymax) / a + intersectPts = [] + if ymin <= y2_at_xmin <= ymax: # valid intersect point + intersectPts.append((xmin, y2_at_xmin)) + if ymin <= y2_at_xmax <= ymax: + intersectPts.append((xmax, y2_at_xmax)) + if xmin <= x2_at_ymin <= xmax: + intersectPts.append((x2_at_ymin, ymin)) + if xmin <= x2_at_ymax <= xmax: + intersectPts.append((x2_at_ymax, ymax)) + # delete duplicate (happens if intersect point is at extent corner) + intersectPts = set(intersectPts) + # choose target intersect point + if leftDir: + pt = min(intersectPts) # smaller x value + else: + pt = max(intersectPts) + return pt + + def inExtent(self, x, y): + xmin, xmax, ymin, ymax = self.extent + return x >= xmin and x <= xmax and y >= ymin and y <= ymax + + def orderPts(self, edges): + poly = [] # returned polygon points list [pt1, pt2, pt3, pt4 ....] + pts = [] + # get points list + for edge in edges: + pts.extend([pt for pt in edge]) + # try to get start & end point + try: + startPt, endPt = [pt for pt in pts if pts.count(pt) < 2] # start and end point aren't duplicate + except: # all points are duplicate --> polygon is complete --> append some or other edge points + complete = True + firstIdx = 0 + poly.append(edges[0][0]) + poly.append(edges[0][1]) + else: # incomplete --> append the first edge points + complete = False + # search first edge + for i, edge in enumerate(edges): + if startPt in edge: # find + firstIdx = i + break + poly.append(edges[firstIdx][0]) + poly.append(edges[firstIdx][1]) + if poly[0] != startPt: + poly.reverse() + # append next points in list + del edges[firstIdx] + while edges: # all points will be treated when edges list will be empty + currentPt = poly[-1] # last item + for i, edge in enumerate(edges): + if currentPt == edge[0]: + poly.append(edge[1]) + break + elif currentPt == edge[1]: + poly.append(edge[0]) + break + del edges[i] + return poly, complete + + def setClipBuffer(self, xpourcent, ypourcent): + xmin, xmax, ymin, ymax = self.extent + witdh = xmax - xmin + height = ymax - ymin + xmin = xmin - witdh * xpourcent / 100 + xmax = xmax + witdh * xpourcent / 100 + ymin = ymin - height * ypourcent / 100 + ymax = ymax + height * ypourcent / 100 + self.extent = xmin, xmax, ymin, ymax + + # End clip functions # + + def outSite(self, s): + if(self.debug): + print("site (%d) at %f %f" % (s.sitenum, s.x, s.y)) + elif(self.triangulate): + pass + elif(self.doPrint): + print("s %f %f" % (s.x, s.y)) + + def outVertex(self, s): + self.vertices.append((s.x, s.y)) + if(self.debug): + print("vertex(%d) at %f %f" % (s.sitenum, s.x, s.y)) + elif(self.triangulate): + pass + elif(self.doPrint): + print("v %f %f" % (s.x, s.y)) + + def outTriple(self, s1, s2, s3): + self.triangles.append((s1.sitenum, s2.sitenum, s3.sitenum)) + if(self.debug): + print("circle through left=%d right=%d bottom=%d" % (s1.sitenum, s2.sitenum, s3.sitenum)) + elif(self.triangulate and self.doPrint): + print("%d %d %d" % (s1.sitenum, s2.sitenum, s3.sitenum)) + + def outBisector(self, edge): + self.lines.append((edge.a, edge.b, edge.c)) + if(self.debug): + print("line(%d) %gx+%gy=%g, bisecting %d %d" % (edge.edgenum, edge.a, edge.b, + edge.c, edge.reg[0].sitenum, + edge.reg[1].sitenum) + ) + elif(self.doPrint): + print("l %f %f %f" % (edge.a, edge.b, edge.c)) + + def outEdge(self, edge): + sitenumL = -1 + if edge.ep[Edge.LE] is not None: + sitenumL = edge.ep[Edge.LE].sitenum + sitenumR = -1 + if edge.ep[Edge.RE] is not None: + sitenumR = edge.ep[Edge.RE].sitenum + + # polygons dict add by CF + if edge.reg[0].sitenum not in self.polygons: + self.polygons[edge.reg[0].sitenum] = [] + if edge.reg[1].sitenum not in self.polygons: + self.polygons[edge.reg[1].sitenum] = [] + self.polygons[edge.reg[0].sitenum].append((edge.edgenum, sitenumL, sitenumR)) + self.polygons[edge.reg[1].sitenum].append((edge.edgenum, sitenumL, sitenumR)) + + self.edges.append((edge.edgenum, sitenumL, sitenumR)) + + if(not self.triangulate): + if(self.doPrint): + print("e %d" % edge.edgenum) + print(" %d " % sitenumL) + print("%d" % sitenumR) + + +def voronoi(siteList, context): + context.extent = siteList.extent + edgeList = EdgeList(siteList.xmin, siteList.xmax, len(siteList)) + priorityQ = PriorityQueue(siteList.ymin, siteList.ymax, len(siteList)) + siteIter = siteList.iterator() + + bottomsite = siteIter.next() + context.outSite(bottomsite) + newsite = siteIter.next() + minpt = Site(-BIG_FLOAT, -BIG_FLOAT) + while True: + if not priorityQ.isEmpty(): + minpt = priorityQ.getMinPt() + + if (newsite and (priorityQ.isEmpty() or newsite < minpt)): + # newsite is smallest - this is a site event + context.outSite(newsite) + + # get first Halfedge to the LEFT and RIGHT of the new site + lbnd = edgeList.leftbnd(newsite) + rbnd = lbnd.right + + # if this halfedge has no edge, bot = bottom site (whatever that is) + # create a new edge that bisects + bot = lbnd.rightreg(bottomsite) + edge = Edge.bisect(bot, newsite) + context.outBisector(edge) + + # create a new Halfedge, setting its pm field to 0 and insert + # this new bisector edge between the left and right vectors in + # a linked list + bisector = Halfedge(edge, Edge.LE) + edgeList.insert(lbnd, bisector) + + # if the new bisector intersects with the left edge, remove + # the left edge's vertex, and put in the new one + p = lbnd.intersect(bisector) + if p is not None: + priorityQ.delete(lbnd) + priorityQ.insert(lbnd, p, newsite.distance(p)) + + # create a new Halfedge, setting its pm field to 1 + # insert the new Halfedge to the right of the original bisector + lbnd = bisector + bisector = Halfedge(edge, Edge.RE) + edgeList.insert(lbnd, bisector) + + # if this new bisector intersects with the right Halfedge + p = bisector.intersect(rbnd) + if p is not None: + # push the Halfedge into the ordered linked list of vertices + priorityQ.insert(bisector, p, newsite.distance(p)) + + newsite = siteIter.next() + + elif not priorityQ.isEmpty(): + # intersection is smallest - this is a vector (circle) event + # pop the Halfedge with the lowest vector off the ordered list of + # vectors. Get the Halfedge to the left and right of the above HE + # and also the Halfedge to the right of the right HE + lbnd = priorityQ.popMinHalfedge() + llbnd = lbnd.left + rbnd = lbnd.right + rrbnd = rbnd.right + + # get the Site to the left of the left HE and to the right of + # the right HE which it bisects + bot = lbnd.leftreg(bottomsite) + top = rbnd.rightreg(bottomsite) + + # output the triple of sites, stating that a circle goes through them + mid = lbnd.rightreg(bottomsite) + context.outTriple(bot, top, mid) + + # get the vertex that caused this event and set the vertex number + # couldn't do this earlier since we didn't know when it would be processed + v = lbnd.vertex + siteList.setSiteNumber(v) + context.outVertex(v) + + # set the endpoint of the left and right Halfedge to be this vector + if lbnd.edge.setEndpoint(lbnd.pm, v): + context.outEdge(lbnd.edge) + + if rbnd.edge.setEndpoint(rbnd.pm, v): + context.outEdge(rbnd.edge) + + # delete the lowest HE, remove all vertex events to do with the + # right HE and delete the right HE + edgeList.delete(lbnd) + priorityQ.delete(rbnd) + edgeList.delete(rbnd) + + # if the site to the left of the event is higher than the Site + # to the right of it, then swap them and set 'pm' to RIGHT + pm = Edge.LE + if bot.y > top.y: + bot, top = top, bot + pm = Edge.RE + + # Create an Edge (or line) that is between the two Sites. This + # creates the formula of the line, and assigns a line number to it + edge = Edge.bisect(bot, top) + context.outBisector(edge) + + # create a HE from the edge + bisector = Halfedge(edge, pm) + + # insert the new bisector to the right of the left HE + # set one endpoint to the new edge to be the vector point 'v' + # If the site to the left of this bisector is higher than the right + # Site, then this endpoint is put in position 0; otherwise in pos 1 + edgeList.insert(llbnd, bisector) + if edge.setEndpoint(Edge.RE - pm, v): + context.outEdge(edge) + + # if left HE and the new bisector don't intersect, then delete + # the left HE, and reinsert it + p = llbnd.intersect(bisector) + if p is not None: + priorityQ.delete(llbnd) + priorityQ.insert(llbnd, p, bot.distance(p)) + + # if right HE and the new bisector don't intersect, then reinsert it + p = bisector.intersect(rrbnd) + if p is not None: + priorityQ.insert(bisector, p, bot.distance(p)) + else: + break + + he = edgeList.leftend.right + while he is not edgeList.rightend: + context.outEdge(he.edge) + he = he.right + Edge.EDGE_NUM = 0 # CF + + +def isEqual(a, b, relativeError=TOLERANCE): + # is nearly equal to within the allowed relative error + norm = max(abs(a), abs(b)) + return (norm < relativeError) or (abs(a - b) < (relativeError * norm)) + + +class Site(object): + + def __init__(self, x=0.0, y=0.0, sitenum=0): + self.x = x + self.y = y + self.sitenum = sitenum + + def dump(self): + print("Site #%d (%g, %g)" % (self.sitenum, self.x, self.y)) + + def __lt__(self, other): + if self.y < other.y: + return True + elif self.y > other.y: + return False + elif self.x < other.x: + return True + elif self.x > other.x: + return False + else: + return False + + def __eq__(self, other): + if self.y == other.y and self.x == other.x: + return True + + def distance(self, other): + dx = self.x - other.x + dy = self.y - other.y + return math.sqrt(dx * dx + dy * dy) + + +class Edge(object): + LE = 0 # left end indice --> edge.ep[Edge.LE] + RE = 1 # right end indice + EDGE_NUM = 0 + DELETED = {} # marker value + + def __init__(self): + self.a = 0.0 # equation of the line a*x+b*y = c + self.b = 0.0 + self.c = 0.0 + self.ep = [None, None] # end point (2 tuples of site) + self.reg = [None, None] + self.edgenum = 0 + + def dump(self): + print("(#%d a=%g, b=%g, c=%g)" % (self.edgenum, self.a, self.b, self.c)) + print("ep", self.ep) + print("reg", self.reg) + + def setEndpoint(self, lrFlag, site): + self.ep[lrFlag] = site + if self.ep[Edge.RE - lrFlag] is None: + return False + return True + + @staticmethod + def bisect(s1, s2): + newedge = Edge() + newedge.reg[0] = s1 # store the sites that this edge is bisecting + newedge.reg[1] = s2 + + # to begin with, there are no endpoints on the bisector - it goes to infinity + # ep[0] and ep[1] are None + + # get the difference in x dist between the sites + dx = float(s2.x - s1.x) + dy = float(s2.y - s1.y) + adx = abs(dx) # make sure that the difference in positive + ady = abs(dy) + + # get the slope of the line + newedge.c = float(s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * 0.5) + if adx > ady: + # set formula of line, with x fixed to 1 + newedge.a = 1.0 + newedge.b = dy / dx + newedge.c /= dx + else: + # set formula of line, with y fixed to 1 + newedge.b = 1.0 + newedge.a = dx / dy + newedge.c /= dy + + newedge.edgenum = Edge.EDGE_NUM + Edge.EDGE_NUM += 1 + return newedge + + +class Halfedge(object): + + def __init__(self, edge=None, pm=Edge.LE): + self.left = None # left Halfedge in the edge list + self.right = None # right Halfedge in the edge list + self.qnext = None # priority queue linked list pointer + self.edge = edge # edge list Edge + self.pm = pm + self.vertex = None # Site() + self.ystar = BIG_FLOAT + + def dump(self): + print("Halfedge--------------------------") + print("left: ", self.left) + print("right: ", self.right) + print("edge: ", self.edge) + print("pm: ", self.pm) + print("vertex: "), + if self.vertex: + self.vertex.dump() + else: + print("None") + print("ystar: ", self.ystar) + + def __lt__(self, other): + if self.ystar < other.ystar: + return True + elif self.ystar > other.ystar: + return False + elif self.vertex.x < other.vertex.x: + return True + elif self.vertex.x > other.vertex.x: + return False + else: + return False + + def __eq__(self, other): + if self.ystar == other.ystar and self.vertex.x == other.vertex.x: + return True + + def leftreg(self, default): + if not self.edge: + return default + elif self.pm == Edge.LE: + return self.edge.reg[Edge.LE] + else: + return self.edge.reg[Edge.RE] + + def rightreg(self, default): + if not self.edge: + return default + elif self.pm == Edge.LE: + return self.edge.reg[Edge.RE] + else: + return self.edge.reg[Edge.LE] + + # returns True if p is to right of halfedge self + def isPointRightOf(self, pt): + e = self.edge + topsite = e.reg[1] + right_of_site = pt.x > topsite.x + + if(right_of_site and self.pm == Edge.LE): + return True + + if(not right_of_site and self.pm == Edge.RE): + return False + + if(e.a == 1.0): + dyp = pt.y - topsite.y + dxp = pt.x - topsite.x + fast = 0 + if ((not right_of_site and e.b < 0.0) or (right_of_site and e.b >= 0.0)): + above = dyp >= e.b * dxp + fast = above + else: + above = pt.x + pt.y * e.b > e.c + if(e.b < 0.0): + above = not above + if (not above): + fast = 1 + if (not fast): + dxs = topsite.x - (e.reg[0]).x + above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b) + if(e.b < 0.0): + above = not above + else: # e.b == 1.0 + yl = e.c - e.a * pt.x + t1 = pt.y - yl + t2 = pt.x - topsite.x + t3 = yl - topsite.y + above = t1 * t1 > t2 * t2 + t3 * t3 + + if(self.pm == Edge.LE): + return above + else: + return not above + + # create a new site where the Halfedges el1 and el2 intersect + def intersect(self, other): + e1 = self.edge + e2 = other.edge + if (e1 is None) or (e2 is None): + return None + + # if the two edges bisect the same parent return None + if e1.reg[1] is e2.reg[1]: + return None + + d = e1.a * e2.b - e1.b * e2.a + if isEqual(d, 0.0): + return None + + xint = (e1.c * e2.b - e2.c * e1.b) / d + yint = (e2.c * e1.a - e1.c * e2.a) / d + if e1.reg[1] < e2.reg[1]: + he = self + e = e1 + else: + he = other + e = e2 + + rightOfSite = xint >= e.reg[1].x + if((rightOfSite and he.pm == Edge.LE) or + (not rightOfSite and he.pm == Edge.RE)): + return None + + # create a new site at the point of intersection - this is a new + # vector event waiting to happen + return Site(xint, yint) + + +class EdgeList(object): + + def __init__(self, xmin, xmax, nsites): + if xmin > xmax: + xmin, xmax = xmax, xmin + self.hashsize = int(2 * math.sqrt(nsites + 4)) + + self.xmin = xmin + self.deltax = float(xmax - xmin) + self.hash = [None] * self.hashsize + + self.leftend = Halfedge() + self.rightend = Halfedge() + self.leftend.right = self.rightend + self.rightend.left = self.leftend + self.hash[0] = self.leftend + self.hash[-1] = self.rightend + + def insert(self, left, he): + he.left = left + he.right = left.right + left.right.left = he + left.right = he + + def delete(self, he): + he.left.right = he.right + he.right.left = he.left + he.edge = Edge.DELETED + + # Get entry from hash table, pruning any deleted nodes + def gethash(self, b): + if(b < 0 or b >= self.hashsize): + return None + he = self.hash[b] + if he is None or he.edge is not Edge.DELETED: + return he + + # Hash table points to deleted half edge. Patch as necessary. + self.hash[b] = None + return None + + def leftbnd(self, pt): + # Use hash table to get close to desired halfedge + bucket = int(((pt.x - self.xmin) / self.deltax * self.hashsize)) + + if(bucket < 0): + bucket = 0 + + if(bucket >= self.hashsize): + bucket = self.hashsize - 1 + + he = self.gethash(bucket) + if(he is None): + i = 1 + while True: + he = self.gethash(bucket - i) + if (he is not None): + break + he = self.gethash(bucket + i) + if (he is not None): + break + i += 1 + + # Now search linear list of halfedges for the corect one + if (he is self.leftend) or (he is not self.rightend and he.isPointRightOf(pt)): + he = he.right + while he is not self.rightend and he.isPointRightOf(pt): + he = he.right + he = he.left + else: + he = he.left + while (he is not self.leftend and not he.isPointRightOf(pt)): + he = he.left + + # Update hash table and reference counts + if(bucket > 0 and bucket < self.hashsize - 1): + self.hash[bucket] = he + return he + + +class PriorityQueue(object): + + def __init__(self, ymin, ymax, nsites): + self.ymin = ymin + self.deltay = ymax - ymin + self.hashsize = int(4 * math.sqrt(nsites)) + self.count = 0 + self.minidx = 0 + self.hash = [] + for i in range(self.hashsize): + self.hash.append(Halfedge()) + + def __len__(self): + return self.count + + def isEmpty(self): + return self.count == 0 + + def insert(self, he, site, offset): + he.vertex = site + he.ystar = site.y + offset + last = self.hash[self.getBucket(he)] + next = last.qnext + while((next is not None) and he > next): + last = next + next = last.qnext + he.qnext = last.qnext + last.qnext = he + self.count += 1 + + def delete(self, he): + if (he.vertex is not None): + last = self.hash[self.getBucket(he)] + while last.qnext is not he: + last = last.qnext + last.qnext = he.qnext + self.count -= 1 + he.vertex = None + + def getBucket(self, he): + bucket = int(((he.ystar - self.ymin) / self.deltay) * self.hashsize) + if bucket < 0: + bucket = 0 + if bucket >= self.hashsize: + bucket = self.hashsize - 1 + if bucket < self.minidx: + self.minidx = bucket + return bucket + + def getMinPt(self): + while(self.hash[self.minidx].qnext is None): + self.minidx += 1 + he = self.hash[self.minidx].qnext + x = he.vertex.x + y = he.ystar + return Site(x, y) + + def popMinHalfedge(self): + curr = self.hash[self.minidx].qnext + self.hash[self.minidx].qnext = curr.qnext + self.count -= 1 + return curr + + +class SiteList(object): + + def __init__(self, pointList): + self.__sites = [] + self.__sitenum = 0 + + self.__xmin = min([pt.x for pt in pointList]) + self.__ymin = min([pt.y for pt in pointList]) + self.__xmax = max([pt.x for pt in pointList]) + self.__ymax = max([pt.y for pt in pointList]) + self.__extent = (self.__xmin, self.__xmax, self.__ymin, self.__ymax) + + for i, pt in enumerate(pointList): + self.__sites.append(Site(pt.x, pt.y, i)) + self.__sites.sort() + + def setSiteNumber(self, site): + site.sitenum = self.__sitenum + self.__sitenum += 1 + + class Iterator(object): + + def __init__(this, lst): + this.generator = (s for s in lst) + + def __iter__(this): + return this + + def next(this): + try: + # Note: Blender is Python 3.x so no need for 2.x checks + return this.generator.__next__() + except StopIteration: + return None + + def iterator(self): + return SiteList.Iterator(self.__sites) + + def __iter__(self): + return SiteList.Iterator(self.__sites) + + def __len__(self): + return len(self.__sites) + + def _getxmin(self): + return self.__xmin + + def _getymin(self): + return self.__ymin + + def _getxmax(self): + return self.__xmax + + def _getymax(self): + return self.__ymax + + def _getextent(self): + return self.__extent + + xmin = property(_getxmin) + ymin = property(_getymin) + xmax = property(_getxmax) + ymax = property(_getymax) + extent = property(_getextent) + + +def computeVoronoiDiagram(points, xBuff=0, yBuff=0, polygonsOutput=False, + formatOutput=False, closePoly=True): + """ + Takes : + - a list of point objects (which must have x and y fields). + - x and y buffer values which are the expansion percentages of the bounding box + rectangle including all input points. + Returns : + - With default options : + A list of 2-tuples, representing the two points of each Voronoi diagram edge. + Each point contains 2-tuples which are the x,y coordinates of point. + if formatOutput is True, returns : + - a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices. + - and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram. + v1 and v2 are the indices of the vertices at the end of the edge. + - If polygonsOutput option is True, returns : + A dictionary of polygons, keys are the indices of the input points, + values contains n-tuples representing the n points of each Voronoi diagram polygon. + Each point contains 2-tuples which are the x,y coordinates of point. + if formatOutput is True, returns : + - A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices. + - and a dictionary of input points indices. Values contains n-tuples representing + the n points of each Voronoi diagram polygon. + Each tuple contains the vertex indices of the polygon vertices. + - if closePoly is True then, in the list of points of a polygon, last point will be the same of first point + """ + siteList = SiteList(points) + context = Context() + voronoi(siteList, context) + context.setClipBuffer(xBuff, yBuff) + if not polygonsOutput: + clipEdges = context.getClipEdges() + if formatOutput: + vertices, edgesIdx = formatEdgesOutput(clipEdges) + return vertices, edgesIdx + else: + return clipEdges + else: + clipPolygons = context.getClipPolygons(closePoly) + if formatOutput: + vertices, polyIdx = formatPolygonsOutput(clipPolygons) + return vertices, polyIdx + else: + return clipPolygons + + +def formatEdgesOutput(edges): + # get list of points + pts = [] + for edge in edges: + pts.extend(edge) + # get unique values + pts = set(pts) # unique values (tuples are hashable) + # get dict {values:index} + valuesIdxDict = dict(zip(pts, range(len(pts)))) + # get edges index reference + edgesIdx = [] + for edge in edges: + edgesIdx.append([valuesIdxDict[pt] for pt in edge]) + return list(pts), edgesIdx + + +def formatPolygonsOutput(polygons): + # get list of points + pts = [] + for poly in polygons.values(): + pts.extend(poly) + # get unique values + pts = set(pts) # unique values (tuples are hashable) + # get dict {values:index} + valuesIdxDict = dict(zip(pts, range(len(pts)))) + # get polygons index reference + polygonsIdx = {} + for inPtsIdx, poly in polygons.items(): + polygonsIdx[inPtsIdx] = [valuesIdxDict[pt] for pt in poly] + return list(pts), polygonsIdx + + +def computeDelaunayTriangulation(points): + """ Takes a list of point objects (which must have x and y fields). + Returns a list of 3-tuples: the indices of the points that form a + Delaunay triangle. + """ + siteList = SiteList(points) + context = Context() + context.triangulate = True + voronoi(siteList, context) + return context.triangles diff --git a/add_advanced_objects/delaunay_voronoi/__init__.py b/add_advanced_objects/delaunay_voronoi/__init__.py new file mode 100644 index 00000000..1d210a2a --- /dev/null +++ b/add_advanced_objects/delaunay_voronoi/__init__.py @@ -0,0 +1,52 @@ +# -*- coding:utf-8 -*- + +# ##### 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": "Delaunay Voronoi", + "description": "Points cloud Delaunay triangulation in 2.5D " + "(suitable for terrain modelling) or Voronoi diagram in 2D", + "author": "Domlysz, Oscurart", + "version": (1, 3), + "blender": (2, 7, 0), + "location": "View3D > Tools > GIS", + "warning": "", + "wiki_url": "https://github.com/domlysz/BlenderGIS/wiki", + "tracker_url": "", + "category": "" + } + +if "bpy" in locals(): + import importlib + importlib.reload(oscurart_constellation) + +else: + from . import oscurart_constellation + +import bpy +from .delaunayVoronoiBlender import ToolsPanelDelaunay + + +# Register +def register(): + bpy.utils.register_module(__name__) + + +def unregister(): + bpy.utils.unregister_module(__name__) diff --git a/add_advanced_objects/delaunay_voronoi/delaunayVoronoiBlender.py b/add_advanced_objects/delaunay_voronoi/delaunayVoronoiBlender.py new file mode 100644 index 00000000..e937e7a1 --- /dev/null +++ b/add_advanced_objects/delaunay_voronoi/delaunayVoronoiBlender.py @@ -0,0 +1,234 @@ +# -*- coding:utf-8 -*- + +import bpy +from .DelaunayVoronoi import ( + computeVoronoiDiagram, + computeDelaunayTriangulation, + ) +from bpy.types import ( + Operator, + Panel, + ) +from bpy.props import EnumProperty + + +class Point: + def __init__(self, x, y, z): + self.x, self.y, self.z = x, y, z + + +def unique(L): + """Return a list of unhashable elements in s, but without duplicates. + [[1, 2], [2, 3], [1, 2]] >>> [[1, 2], [2, 3]]""" + # For unhashable objects, you can sort the sequence and + # then scan from the end of the list, deleting duplicates as you go + nDupli = 0 + nZcolinear = 0 + # sort() brings the equal elements together; then duplicates + # are easy to weed out in a single pass + L.sort() + last = L[-1] + for i in range(len(L) - 2, -1, -1): + if last[:2] == L[i][:2]: # XY coordinates compararison + if last[2] == L[i][2]: # Z coordinates compararison + nDupli += 1 # duplicates vertices + else: # Z colinear + nZcolinear += 1 + del L[i] + else: + last = L[i] + # list data type is mutable, input list will automatically update + # and doesn't need to be returned + return (nDupli, nZcolinear) + + +def checkEqual(lst): + return lst[1:] == lst[:-1] + + +class ToolsPanelDelaunay(Panel): + bl_category = "Create" + bl_label = "Delaunay Voronoi" + bl_space_type = "VIEW_3D" + bl_context = "objectmode" + bl_region_type = "TOOLS" + bl_options = {"DEFAULT_CLOSED"} + + def draw(self, context): + layout = self.layout + layout.label('Constellation') + self.layout.operator("delaunay.triangulation") + self.layout.operator("voronoi.tesselation") + layout.label('Constellation') + layout.operator("mesh.constellation", text="Cross Section") + + +class OBJECT_OT_TriangulateButton(Operator): + bl_idname = "delaunay.triangulation" + bl_label = "Triangulation" + bl_description = "Terrain points cloud Delaunay triangulation in 2.5D" + bl_options = {"UNDO"} + + def execute(self, context): + # Get selected obj + objs = bpy.context.selected_objects + if len(objs) == 0 or len(objs) > 1: + self.report({'INFO'}, "Selection is empty or too much object selected") + return {'FINISHED'} + + obj = objs[0] + if obj.type != 'MESH': + self.report({'INFO'}, "Selection isn't a mesh") + return {'FINISHED'} + + # Get points coodinates + r = obj.rotation_euler + s = obj.scale + mesh = obj.data + vertsPts = [vertex.co for vertex in mesh.vertices] + # Remove duplicate + verts = [[vert.x, vert.y, vert.z] for vert in vertsPts] + nDupli, nZcolinear = unique(verts) + nVerts = len(verts) + print(str(nDupli) + " duplicates points ignored") + print(str(nZcolinear) + " z colinear points excluded") + if nVerts < 3: + self.report({'ERROR'}, "Not enough points") + return {'FINISHED'} + + # Check colinear + xValues = [pt[0] for pt in verts] + yValues = [pt[1] for pt in verts] + + if checkEqual(xValues) or checkEqual(yValues): + self.report({'ERROR'}, "Points are colinear") + return {'FINISHED'} + + # Triangulate + print("Triangulate " + str(nVerts) + " points...") + vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts] + triangles = computeDelaunayTriangulation(vertsPts) + # reverse point order --> if all triangles are specified anticlockwise then all faces up + triangles = [tuple(reversed(tri)) for tri in triangles] + + print(str(len(triangles)) + " triangles") + + # Create new mesh structure + print("Create mesh...") + tinMesh = bpy.data.meshes.new("TIN") # create a new mesh + tinMesh.from_pydata(verts, [], triangles) # Fill the mesh with triangles + tinMesh.update(calc_edges=True) # Update mesh with new data + + # Create an object with that mesh + tinObj = bpy.data.objects.new("TIN", tinMesh) + # Place object + tinObj.location = obj.location.copy() + tinObj.rotation_euler = r + tinObj.scale = s + # Update scene + bpy.context.scene.objects.link(tinObj) # Link object to scene + bpy.context.scene.objects.active = tinObj + tinObj.select = True + obj.select = False + # Report + self.report({'INFO'}, "Mesh created (" + str(len(triangles)) + " triangles)") + return {'FINISHED'} + + +class OBJECT_OT_VoronoiButton(Operator): + bl_idname = "voronoi.tesselation" + bl_label = "Diagram" + bl_description = "Points cloud Voronoi diagram in 2D" + bl_options = {"REGISTER", "UNDO"} + + meshType = EnumProperty( + items=[("Edges", "Edges", ""), ("Faces", "Faces", "")], + name="Mesh type", + description="" + ) + + def execute(self, context): + # Get selected obj + objs = bpy.context.selected_objects + if len(objs) == 0 or len(objs) > 1: + self.report({'INFO'}, "Selection is empty or too much object selected") + return {'FINISHED'} + + obj = objs[0] + if obj.type != 'MESH': + self.report({'INFO'}, "Selection isn't a mesh") + return {'FINISHED'} + + # Get points coodinates + r = obj.rotation_euler + s = obj.scale + mesh = obj.data + vertsPts = [vertex.co for vertex in mesh.vertices] + + # Remove duplicate + verts = [[vert.x, vert.y, vert.z] for vert in vertsPts] + nDupli, nZcolinear = unique(verts) + nVerts = len(verts) + + print(str(nDupli) + " duplicates points ignored") + print(str(nZcolinear) + " z colinear points excluded") + + if nVerts < 3: + self.report({'ERROR'}, "Not enough points") + return {'FINISHED'} + + # Check colinear + xValues = [pt[0] for pt in verts] + yValues = [pt[1] for pt in verts] + if checkEqual(xValues) or checkEqual(yValues): + self.report({'ERROR'}, "Points are colinear") + return {'FINISHED'} + + # Create diagram + print("Tesselation... (" + str(nVerts) + " points)") + xbuff, ybuff = 5, 5 + zPosition = 0 + vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts] + if self.meshType == "Edges": + pts, edgesIdx = computeVoronoiDiagram( + vertsPts, xbuff, ybuff, polygonsOutput=False, formatOutput=True + ) + else: + pts, polyIdx = computeVoronoiDiagram( + vertsPts, xbuff, ybuff, polygonsOutput=True, + formatOutput=True, closePoly=False + ) + + pts = [[pt[0], pt[1], zPosition] for pt in pts] + + # Create new mesh structure + voronoiDiagram = bpy.data.meshes.new("VoronoiDiagram") # create a new mesh + + if self.meshType == "Edges": + # Fill the mesh with triangles + voronoiDiagram.from_pydata(pts, edgesIdx, []) + else: + # Fill the mesh with triangles + voronoiDiagram.from_pydata(pts, [], list(polyIdx.values())) + + voronoiDiagram.update(calc_edges=True) # Update mesh with new data + # create an object with that mesh + voronoiObj = bpy.data.objects.new("VoronoiDiagram", voronoiDiagram) + # place object + voronoiObj.location = obj.location.copy() + voronoiObj.rotation_euler = r + voronoiObj.scale = s + + # update scene + bpy.context.scene.objects.link(voronoiObj) # Link object to scene + bpy.context.scene.objects.active = voronoiObj + voronoiObj.select = True + obj.select = False + + # Report + if self.meshType == "Edges": + self.report({'INFO'}, "Mesh created (" + str(len(edgesIdx)) + " edges)") + else: + self.report({'INFO'}, "Mesh created (" + str(len(polyIdx)) + " polygons)") + + return {'FINISHED'} diff --git a/add_advanced_objects/delaunay_voronoi/oscurart_constellation.py b/add_advanced_objects/delaunay_voronoi/oscurart_constellation.py new file mode 100644 index 00000000..babbfdc5 --- /dev/null +++ b/add_advanced_objects/delaunay_voronoi/oscurart_constellation.py @@ -0,0 +1,106 @@ +# ##### 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": "Mesh: Constellation", + "author": "Oscurart", + "version": (1, 0), + "blender": (2, 67, 0), + "location": "Add > Mesh > Constellation", + "description": "Adds a new Mesh From Selected", + "warning": "", + "wiki_url": "", + "tracker_url": "", + "category": "Add Mesh"} + +import bpy +from bpy.types import Operator +from bpy.props import FloatProperty +from math import sqrt + + +def VertDis(a, b): + dst = sqrt(pow(a.co.x - b.co.x, 2) + + pow(a.co.y - b.co.y, 2) + + pow(a.co.z - b.co.z, 2)) + return(dst) + + +def OscConstellation(limit): + actobj = bpy.context.object + vertlist = [] + edgelist = [] + edgei = 0 + + for ind, verta in enumerate(actobj.data.vertices[:]): + for vertb in actobj.data.vertices[ind:]: + if VertDis(verta, vertb) <= limit: + vertlist.append(verta.co[:]) + vertlist.append(vertb.co[:]) + edgelist.append((edgei, edgei + 1)) + edgei += 2 + + mesh = bpy.data.meshes.new("rsdata") + object = bpy.data.objects.new("rsObject", mesh) + bpy.context.scene.objects.link(object) + mesh.from_pydata(vertlist, edgelist, []) + + +class Oscurart_Constellation (Operator): + bl_idname = "mesh.constellation" + bl_label = "Constellation" + bl_description = "Create a Constellation Mesh" + bl_options = {'REGISTER', 'UNDO'} + + limit = FloatProperty( + name='Limit', + default=2, + min=0 + ) + + @classmethod + def poll(cls, context): + return(bpy.context.active_object.type == "MESH") + + def execute(self, context): + OscConstellation(self.limit) + + return {'FINISHED'} + + +# Register + +def add_osc_constellation_button(self, context): + self.layout.operator( + Oscurart_Constellation.bl_idname, + text="Constellation", + icon="PLUGIN") + + +def register(): + bpy.utils.register_class(Oscurart_Constellation) + bpy.types.INFO_MT_mesh_add.append(add_osc_constellation_button) + + +def unregister(): + bpy.utils.unregister_class(Oscurart_Constellation) + bpy.types.INFO_MT_mesh_add.remove(add_osc_constellation_button) + + +if __name__ == '__main__': + register() diff --git a/add_advanced_objects/drop_to_ground.py b/add_advanced_objects/drop_to_ground.py new file mode 100644 index 00000000..801b7e94 --- /dev/null +++ b/add_advanced_objects/drop_to_ground.py @@ -0,0 +1,315 @@ +# ##### 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": "Drop to Ground1", + "author": "Unnikrishnan(kodemax), Florian Meyer(testscreenings)", + "version": (1, 2), + "blender": (2, 71, 0), + "location": "3D View > Toolshelf > Tools Tab", + "description": "Drop selected objects on active object", + "warning": "", + "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/" + "Scripts/Object/Drop_to_ground", + "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", + "category": "Object"} + + +import bpy +import bmesh +from mathutils import ( + Vector, + Matrix, + ) +from bpy.types import ( + Operator, + Panel, + ) +from bpy.props import BoolProperty + + +def get_align_matrix(location, normal): + up = Vector((0, 0, 1)) + angle = normal.angle(up) + axis = up.cross(normal) + mat_rot = Matrix.Rotation(angle, 4, axis) + mat_loc = Matrix.Translation(location) + mat_align = mat_rot * mat_loc + return mat_align + + +def transform_ground_to_world(sc, ground): + tmpMesh = ground.to_mesh(sc, True, 'PREVIEW') + tmpMesh.transform(ground.matrix_world) + tmp_ground = bpy.data.objects.new('tmpGround', tmpMesh) + sc.objects.link(tmp_ground) + sc.update() + return tmp_ground + + +def get_lowest_world_co_from_mesh(ob, mat_parent=None): + bme = bmesh.new() + bme.from_mesh(ob.data) + mat_to_world = ob.matrix_world.copy() + if mat_parent: + mat_to_world = mat_parent * mat_to_world + lowest = None + for v in bme.verts: + if not lowest: + lowest = v + if (mat_to_world * v.co).z < (mat_to_world * lowest.co).z: + lowest = v + lowest_co = mat_to_world * lowest.co + bme.free() + + return lowest_co + + +def get_lowest_world_co(context, ob, mat_parent=None): + if ob.type == 'MESH': + return get_lowest_world_co_from_mesh(ob) + + elif ob.type == 'EMPTY' and ob.dupli_type == 'GROUP': + if not ob.dupli_group: + return None + + else: + lowest_co = None + for ob_l in ob.dupli_group.objects: + if ob_l.type == 'MESH': + lowest_ob_l = get_lowest_world_co_from_mesh(ob_l, ob.matrix_world) + if not lowest_co: + lowest_co = lowest_ob_l + if lowest_ob_l.z < lowest_co.z: + lowest_co = lowest_ob_l + + return lowest_co + + +def drop_objectsall(self, context): + ground = bpy.context.active_object + name = ground.name + + for obs in bpy.context.scene.objects: + obs.select = True + if obs.name == name: + obs.select = False + + obs2 = context.selected_objects + + tmp_ground = transform_ground_to_world(context.scene, ground) + down = Vector((0, 0, -10000)) + + for ob in obs2: + if self.use_origin: + lowest_world_co = ob.location + else: + lowest_world_co = get_lowest_world_co(context, ob) + if not lowest_world_co: + print(ob.type, 'is not supported. Failed to drop', ob.name) + continue + is_hit, hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co, down) + if not is_hit: + print(ob.name, 'didn\'t hit the ground') + continue + + # simple drop down + to_ground_vec = hit_location - lowest_world_co + ob.location += to_ground_vec + + # drop with align to hit normal + if self.align: + to_center_vec = ob.location - hit_location # vec: hit_loc to origin + # rotate object to align with face normal + mat_normal = get_align_matrix(hit_location, hit_normal) + rot_euler = mat_normal.to_euler() + mat_ob_tmp = ob.matrix_world.copy().to_3x3() + mat_ob_tmp.rotate(rot_euler) + mat_ob_tmp = mat_ob_tmp.to_4x4() + ob.matrix_world = mat_ob_tmp + # move_object to hit_location + ob.location = hit_location + # move object above surface again + to_center_vec.rotate(rot_euler) + ob.location += to_center_vec + + # cleanup + bpy.ops.object.select_all(action='DESELECT') + tmp_ground.select = True + bpy.ops.object.delete('EXEC_DEFAULT') + for ob in obs2: + ob.select = True + ground.select = True + + +def drop_objects(self, context): + ground = context.object + obs = context.selected_objects + obs.remove(ground) + tmp_ground = transform_ground_to_world(context.scene, ground) + down = Vector((0, 0, -10000)) + + for ob in obs: + if self.use_origin: + lowest_world_co = ob.location + else: + lowest_world_co = get_lowest_world_co(context, ob) + if not lowest_world_co: + print(ob.type, 'is not supported. Failed to drop', ob.name) + continue + is_hit, hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co, down) + if not is_hit: + print(ob.name, 'didn\'t hit the ground') + continue + + # simple drop down + to_ground_vec = hit_location - lowest_world_co + ob.location += to_ground_vec + + # drop with align to hit normal + if self.align: + to_center_vec = ob.location - hit_location # vec: hit_loc to origin + # rotate object to align with face normal + mat_normal = get_align_matrix(hit_location, hit_normal) + rot_euler = mat_normal.to_euler() + mat_ob_tmp = ob.matrix_world.copy().to_3x3() + mat_ob_tmp.rotate(rot_euler) + mat_ob_tmp = mat_ob_tmp.to_4x4() + ob.matrix_world = mat_ob_tmp + # move_object to hit_location + ob.location = hit_location + # move object above surface again + to_center_vec.rotate(rot_euler) + ob.location += to_center_vec + + # cleanup + bpy.ops.object.select_all(action='DESELECT') + tmp_ground.select = True + bpy.ops.object.delete('EXEC_DEFAULT') + for ob in obs: + ob.select = True + ground.select = True + + +class OBJECT_OT_drop_to_ground(Operator): + bl_idname = "object.drop_on_active" + bl_label = "Drop to Ground" + bl_options = {'REGISTER', 'UNDO'} + bl_description = "Drop selected objects on active object" + + align = BoolProperty( + name="Align to ground", + description="Aligns the object to the ground", + default=True) + use_origin = BoolProperty( + name="Use Center", + description="Drop to objects origins", + default=False) + + @classmethod + def poll(cls, context): + return len(context.selected_objects) >= 2 + + def execute(self, context): + print('\nDropping Objects') + drop_objects(self, context) + return {'FINISHED'} + + +class OBJECT_OT_drop_all_ground(Operator): + bl_idname = "object.drop_all_active" + bl_label = "Drop to Ground" + bl_options = {'REGISTER', 'UNDO'} + bl_description = "Drop selected objects on active object" + + align = BoolProperty( + name="Align to ground", + description="Aligns the object to the ground", + default=True) + use_origin = BoolProperty( + name="Use Center", + description="Drop to objects origins", + default=False) + + def execute(self, context): + print('\nDropping Objects') + drop_objectsall(self, context) + + return {'FINISHED'} + + +class drop_help(Operator): + bl_idname = "help.drop" + bl_label = "" + + def draw(self, context): + layout = self.layout + layout.label("To use:") + layout.label("___________________________") + + layout.label("Drop selected :-") + + layout.label("Name the base object 'Ground'") + layout.label("Select the object/s to drop") + layout.label("Then Shift Select 'Ground'") + layout.label("___________________________") + + layout.label("Drop all :-") + + layout.label("select the ground mesh , and press Drop all") + + def execute(self, context): + return {'FINISHED'} + + def invoke(self, context, event): + return context.window_manager.invoke_popup(self, width=300) + + +class Drop_Operator_Panel(Panel): + bl_label = "Drop To Ground" + bl_region_type = "TOOLS" + bl_space_type = "VIEW_3D" + bl_options = {'DEFAULT_CLOSED'} + bl_context = "objectmode" + bl_category = "Create" + + def draw(self, context): + layout = self.layout + row = layout.row() + row = layout.split(0.80) + row.operator(OBJECT_OT_drop_to_ground.bl_idname, + text="Drop Selected") + row = layout.row() + row.operator(OBJECT_OT_drop_all_ground.bl_idname, + text="Drop All") + row.operator('help.drop', icon='INFO') + + +# Register +def register(): + bpy.utils.register_module(__name__) + pass + + +def unregister(): + bpy.utils.unregister_module(__name__) + pass + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/make_struts.py b/add_advanced_objects/make_struts.py new file mode 100644 index 00000000..7f754ea8 --- /dev/null +++ b/add_advanced_objects/make_struts.py @@ -0,0 +1,571 @@ +# 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> + +""" +bl_info = { + "name": "Strut Generator", + "author": "Bill Currie", + "blender": (2, 6, 3), + "api": 35622, + "location": "View3D > Add > Mesh > Struts", + "description": "Add struts meshes based on selected truss meshes", + "warning": "can get very high-poly", + "wiki_url": "", + "tracker_url": "", + "category": "Add Mesh"} +""" + +import bpy +import bmesh +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 (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, id, od, n, solid, loops): + v1 = Vector(v1) + v2 = Vector(v2) + axis = v2 - v1 + pos = [(0, od / 2)] + if loops: + pos += [((od - id) / 2, od / 2), + (axis.length - (od - id) / 2, od / 2)] + pos += [(axis.length, od / 2)] + if solid: + pos += [(axis.length, id / 2)] + if loops: + pos += [(axis.length - (od - id) / 2, id / 2), + ((od - id) / 2, id / 2)] + pos += [(0, id / 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] + # print(verts,faces) + 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, id, 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, id, 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 + # print(verts,faces) + return verts, faces + + +def create_struts(self, context, id, od, segments, solid, loops, manifold): + build_cossin(segments) + + bpy.context.user_preferences.edit.use_global_undo = False + 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, id, 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() + bpy.context.user_preferences.edit.use_global_undo = True + return {'FINISHED'} + + +class Struts(bpy.types.Operator): + """Add one or more struts meshes based on selected truss meshes""" + bl_idname = "mesh.generate_struts" + bl_label = "Struts" + bl_description = """Add one or more struts meshes based on selected truss meshes""" + bl_options = {'REGISTER', 'UNDO'} + + id = 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 execute(self, context): + keywords = self.as_keywords() + return create_struts(self, context, **keywords) + + +def menu_func(self, context): + self.layout.operator(Struts.bl_idname, text="Struts", icon='PLUGIN') + + +def register(): + bpy.utils.register_module(__name__) + bpy.types.INFO_MT_mesh_add.append(menu_func) + + +def unregister(): + bpy.utils.unregister_module(__name__) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/mesh_easylattice.py b/add_advanced_objects/mesh_easylattice.py new file mode 100644 index 00000000..13512adf --- /dev/null +++ b/add_advanced_objects/mesh_easylattice.py @@ -0,0 +1,381 @@ +# ##### 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": "Easy Lattice Object", + "author": "Kursad Karatas", + "version": (0, 5), + "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.props import ( + EnumProperty, + IntProperty, + ) + + +# 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 + bpy.types.Scene.activelatticeobject = bpy.props.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(bpy.types.Operator): + """Adds a Lattice modifier ready to edit""" + bl_idname = "object.easy_lattice" + bl_label = "Easy Lattice Creator" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + + lat_u = IntProperty( + name="Lattice u", + default=3 + ) + lat_w = IntProperty( + name="Lattice w", + default=3 + ) + lat_m = IntProperty( + name="Lattice m", + default=3 + ) + lat_types = (('0', 'KEY_LINEAR', '0'), + ('1', 'KEY_CARDINAL', '1'), + ('2', 'KEY_BSPLINE', '2')) + lat_type = EnumProperty( + name="Lattice Type", + items=lat_types, + default='0' + ) + + @classmethod + def poll(cls, context): + return context.active_object is not None + + def execute(self, context): + lat_u = self.lat_u + lat_w = self.lat_w + lat_m = self.lat_m + + # this is a reference to the "items" used to generate the + # enum property + lat_type = self.lat_types[int(self.lat_type)][1] + lat_props = [lat_u, lat_w, lat_m, lat_type] + + main(context, lat_props) + + return {'FINISHED'} + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) + + +def menu_draw(self, context): + self.layout.operator_context = 'INVOKE_REGION_WIN' + self.layout.operator(EasyLattice.bl_idname, "Easy Lattice") + + +def register(): + bpy.utils.register_class(EasyLattice) + bpy.types.VIEW3D_MT_edit_mesh_specials.append(menu_draw) + + +def unregister(): + bpy.utils.unregister_class(EasyLattice) + bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_draw) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/object_add_chain.py b/add_advanced_objects/object_add_chain.py new file mode 100644 index 00000000..57babfd6 --- /dev/null +++ b/add_advanced_objects/object_add_chain.py @@ -0,0 +1,170 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +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, + Panel, + ) + + +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): + Add_Chain() + + return {'FINISHED'} + + +def register(): + bpy.utils.register_module(__name__) + pass + + +def unregister(): + bpy.utils.unregister_module(__name__) + pass + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/object_laplace_lightning.py b/add_advanced_objects/object_laplace_lightning.py new file mode 100644 index 00000000..ceaf6cd6 --- /dev/null +++ b/add_advanced_objects/object_laplace_lightning.py @@ -0,0 +1,1370 @@ +# ##### 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 ##### + +# NOTE Needs cleanup, reorganizing, make prints optional + +bl_info = { + "name": "Laplacian Lightning", + "author": "teldredge", + "version": (0, 2, 7), + "blender": (2, 71, 0), + "location": "View3D > Toolshelf > Create Tab", + "description": "Lightning mesh generator using laplacian growth algorithm", + "warning": "Beta", + "wiki_url": "http://www.funkboxing.com/wordpress/?p=301", + "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", + "category": "Object"} + +# BLENDER LAPLACIAN LIGHTNING +# teldredge +# www.funkboxing.com +# https://developer.blender.org/T27189 + +# using algorithm from +# FAST SIMULATION OF LAPLACIAN GROWTH (FSLG) +# http://gamma.cs.unc.edu/FRAC/ + +# and a few ideas ideas from +# FAST ANIMATION OF LIGHTNING USING AN ADAPTIVE MESH (FALUAM) +# http://gamma.cs.unc.edu/FAST_LIGHTNING/ + + +""" +----- RELEASE LOG/NOTES/PONTIFICATIONS ----- +v0.1.0 - 04.11.11 + basic generate functions and UI + object creation report (Custom Properties: FSLG_REPORT) +v0.2.0 - 04.15.11 + started spelling laplacian right. + add curve function (not in UI) ...twisting problem + classify stroke by MAIN path, h-ORDER paths, TIP paths + jitter cells for mesh creation + add materials if present +v0.2.1 - 04.16.11 + mesh classification speedup +v0.2.2 - 04.21.11 + fxns to write/read array to file + restrict growth to insulator cells (object bounding box) + origin/ground defineable by object + gridunit more like 'resolution' +v0.2.3 - 04.24.11 + cloud attractor object (termintates loop if hit) + secondary path orders (hOrder) disabled in UI (set to 1) +v0.2.4 - 04.26.11 + fixed object selection in UI + will not run if required object not selected + moved to view 3d > toolbox +v0.2.5 - 05.08.11 + testing for 2.57b + single mesh output (for build modifier) + speedups (dist fxn) +v0.2.6 - 06.20.11 + scale/pos on 'write to cubes' works now + if origin obj is mesh, uses all verts as initial charges + semi-helpful tooltips + speedups, faster dedupe fxn, faster classification + use any shape mesh obj as insulator mesh + must have rot=0, scale=1, origin set to geometry + often fails to block bolt with curved/complex shapes + separate single and multi mesh creation +v0.2.7 - 01.05.13 + fixed the issue that prevented enabling the add-on + fixed makeMeshCube fxn + disabled visualization for voxels + +v0.x - + -prevent create_setup_objects from generating duplicates + -fix vis fxn to only buildCPGraph once for VM or VS + -improve list fxns (rid of ((x,y,z),w) and use (x,y,z,w)), use 'sets' + -create python cmodule for a few of most costly fxns + i have pretty much no idea how to do this yet + -cloud and insulator can be groups of MESH objs + -text output, possibly to save on interrupt, allow continue from text + -?hook modifiers from tips->sides->main, weight w/ vert groups + -user defined 'attractor' path + -fix add curve function + -animated arcs via. ionization path + -environment map boundary conditions - requires Eqn. 15 from FSLG. + -assign wattage at each segment for HDRI + -?default settings for -lightning, -teslacoil, -spark/arc + -fix hOrder functionality + -multiple 'MAIN' brances for non-lightning discharges + -n-symmetry option, create mirror images, snowflakes, etc... +""" + +import bpy +import time +import random +from math import sqrt +from mathutils import Vector +import struct +import bisect +import os.path +notZero = 0.0000000001 +winmgr = bpy.context.window_manager + + +# UTILITY FXNS # + +def within(x, y, d): + # CHECK IF x-d <= y <= x+d + if x - d <= y and x + d >= y: + return True + else: + return False + + +def dist(ax, ay, az, bx, by, bz): + dv = Vector((ax, ay, az)) - Vector((bx, by, bz)) + d = dv.length + return d + + +def splitList(aList, idx): + ll = [] + for x in aList: + ll.append(x[idx]) + return ll + + +def splitListCo(aList): + ll = [] + for p in aList: + ll.append((p[0], p[1], p[2])) + return ll + + +def getLowHigh(aList): + tLow = aList[0] + tHigh = aList[0] + for a in aList: + if a < tLow: + tLow = a + if a > tHigh: + tHigh = a + return tLow, tHigh + + +def weightedRandomChoice(aList): + tL = [] + tweight = 0 + for a in range(len(aList)): + idex = a + weight = aList[a] + if weight > 0.0: + tweight += weight + tL.append((tweight, idex)) + i = bisect.bisect(tL, (random.uniform(0, tweight), None)) + r = tL[i][1] + return r + + +def getStencil3D_26(x, y, z): + nL = [] + for xT in range(x - 1, x + 2): + for yT in range(y - 1, y + 2): + for zT in range(z - 1, z + 2): + nL.append((xT, yT, zT)) + nL.remove((x, y, z)) + return nL + + +def jitterCells(aList, jit): + j = jit / 2 + bList = [] + for a in aList: + ax = a[0] + random.uniform(-j, j) + ay = a[1] + random.uniform(-j, j) + az = a[2] + random.uniform(-j, j) + bList.append((ax, ay, az)) + return bList + + +def deDupe(seq, idfun=None): + # THANKS TO THIS GUY - http://www.peterbe.com/plog/uniqifiers-benchmark + if idfun is None: + def idfun(x): + return x + seen = {} + result = [] + for item in seq: + marker = idfun(item) + if marker in seen: + continue + seen[marker] = 1 + result.append(item) + return result + + +# VISUALIZATION FXNS # + +def writeArrayToVoxel(arr, filename): + gridS = 64 + half = int(gridS / 2) + bitOn = 255 + aGrid = [[[0 for z in range(gridS)] for y in range(gridS)] for x in range(gridS)] + for a in arr: + try: + aGrid[a[0] + half][a[1] + half][a[2] + half] = bitOn + except: + print('Particle beyond voxel domain') + file = open(filename, "wb") + for z in range(gridS): + for y in range(gridS): + for x in range(gridS): + file.write(struct.pack('B', aGrid[x][y][z])) + file.flush() + file.close() + + +def writeArrayToFile(arr, filename): + file = open(filename, "w") + for a in arr: + tstr = str(a[0]) + ',' + str(a[1]) + ',' + str(a[2]) + '\n' + file.write(tstr) + file.close + + +def readArrayFromFile(filename): + file = open(filename, "r") + arr = [] + for f in file: + pt = f[0:-1].split(',') + arr.append((int(pt[0]), int(pt[1]), int(pt[2]))) + return arr + + +def makeMeshCube_OLD(msize): + msize = msize / 2 + mmesh = bpy.data.meshes.new('q') + mmesh.vertices.add(8) + mmesh.vertices[0].co = [-msize, -msize, -msize] + mmesh.vertices[1].co = [-msize, msize, -msize] + mmesh.vertices[2].co = [msize, msize, -msize] + mmesh.vertices[3].co = [msize, -msize, -msize] + mmesh.vertices[4].co = [-msize, -msize, msize] + mmesh.vertices[5].co = [-msize, msize, msize] + mmesh.vertices[6].co = [msize, msize, msize] + mmesh.vertices[7].co = [msize, -msize, msize] + mmesh.faces.add(6) + mmesh.faces[0].vertices_raw = [0, 1, 2, 3] + mmesh.faces[1].vertices_raw = [0, 4, 5, 1] + mmesh.faces[2].vertices_raw = [2, 1, 5, 6] + mmesh.faces[3].vertices_raw = [3, 2, 6, 7] + mmesh.faces[4].vertices_raw = [0, 3, 7, 4] + mmesh.faces[5].vertices_raw = [5, 4, 7, 6] + mmesh.update(calc_edges=True) + + return(mmesh) + + +def makeMeshCube(msize): + m2 = msize / 2 + # verts = [(0,0,0),(0,5,0),(5,5,0),(5,0,0),(0,0,5),(0,5,5),(5,5,5),(5,0,5)] + verts = [(-m2, -m2, -m2), (-m2, m2, -m2), (m2, m2, -m2), (m2, -m2, -m2), + (-m2, -m2, m2), (-m2, m2, m2), (m2, m2, m2), (m2, -m2, m2)] + faces = [(0, 1, 2, 3), (4, 5, 6, 7), (0, 4, 5, 1), (1, 5, 6, 2), (2, 6, 7, 3), (3, 7, 4, 0)] + + # Define mesh and object + mmesh = bpy.data.meshes.new("Cube") + # mobject = bpy.data.objects.new("Cube", mmesh) + + # Set location and scene of object + # mobject.location = bpy.context.scene.cursor_location + # bpy.context.scene.objects.link(mobject) + + # Create mesh + mmesh.from_pydata(verts, [], faces) + mmesh.update(calc_edges=True) + return(mmesh) + + +def writeArrayToCubes(arr, gridBU, orig, cBOOL=False, jBOOL=True): + for a in arr: + x = a[0] + y = a[1] + z = a[2] + me = makeMeshCube(gridBU) + ob = bpy.data.objects.new('xCUBE', me) + ob.location.x = (x * gridBU) + orig[0] + ob.location.y = (y * gridBU) + orig[1] + ob.location.z = (z * gridBU) + orig[2] + if cBOOL: # MOSTLY UNUSED + # POS+BLUE, NEG-RED, ZERO:BLACK + col = (1.0, 1.0, 1.0, 1.0) + if a[3] == 0: + col = (0.0, 0.0, 0.0, 1.0) + if a[3] < 0: + col = (-a[3], 0.0, 0.0, 1.0) + if a[3] > 0: + col = (0.0, 0.0, a[3], 1.0) + ob.color = col + bpy.context.scene.objects.link(ob) + bpy.context.scene.update() + if jBOOL: + # SELECTS ALL CUBES w/ ?bpy.ops.object.join() b/c + # CAN'T JOIN ALL CUBES TO A SINGLE MESH RIGHT... ARGH... + for q in bpy.context.scene.objects: + q.select = False + if q.name[0:5] == 'xCUBE': + q.select = True + bpy.context.scene.objects.active = q + + +def addVert(ob, pt, conni=-1): + mmesh = ob.data + mmesh.vertices.add(1) + vcounti = len(mmesh.vertices) - 1 + mmesh.vertices[vcounti].co = [pt[0], pt[1], pt[2]] + if conni > -1: + mmesh.edges.add(1) + ecounti = len(mmesh.edges) - 1 + mmesh.edges[ecounti].vertices = [conni, vcounti] + mmesh.update() + + +def addEdge(ob, va, vb): + mmesh = ob.data + mmesh.edges.add(1) + ecounti = len(mmesh.edges) - 1 + mmesh.edges[ecounti].vertices = [va, vb] + mmesh.update() + + +def newMesh(mname): + mmesh = bpy.data.meshes.new(mname) + omesh = bpy.data.objects.new(mname, mmesh) + bpy.context.scene.objects.link(omesh) + return omesh + + +def writeArrayToMesh(mname, arr, gridBU, rpt=None): + mob = newMesh(mname) + mob.scale = (gridBU, gridBU, gridBU) + if rpt: + addReportProp(mob, rpt) + addVert(mob, arr[0], -1) + for ai in range(1, len(arr)): + a = arr[ai] + addVert(mob, a, ai - 1) + return mob + + +# OUT OF ORDER - SOME PROBLEM WITH IT ADDING (0,0,0) +def writeArrayToCurves(cname, arr, gridBU, bd=.05, rpt=None): + cur = bpy.data.curves.new('fslg_curve', 'CURVE') + cur.use_fill_front = False + cur.use_fill_back = False + cur.bevel_depth = bd + cur.bevel_resolution = 2 + cob = bpy.data.objects.new(cname, cur) + cob.scale = (gridBU, gridBU, gridBU) + if rpt: + addReportProp(cob, rpt) + bpy.context.scene.objects.link(cob) + cur.splines.new('BEZIER') + cspline = cur.splines[0] + div = 1 # SPACING FOR HANDLES (2 - 1/2 WAY, 1 - NEXT BEZIER) + for a in range(len(arr)): + cspline.bezier_points.add(1) + bp = cspline.bezier_points[len(cspline.bezier_points) - 1] + if a - 1 < 0: + hL = arr[a] + else: + hx = arr[a][0] - ((arr[a][0] - arr[a - 1][0]) / div) + hy = arr[a][1] - ((arr[a][1] - arr[a - 1][1]) / div) + hz = arr[a][2] - ((arr[a][2] - arr[a - 1][2]) / div) + hL = (hx, hy, hz) + + if a + 1 > len(arr) - 1: + hR = arr[a] + else: + hx = arr[a][0] + ((arr[a + 1][0] - arr[a][0]) / div) + hy = arr[a][1] + ((arr[a + 1][1] - arr[a][1]) / div) + hz = arr[a][2] + ((arr[a + 1][2] - arr[a][2]) / div) + hR = (hx, hy, hz) + bp.co = arr[a] + bp.handle_left = hL + bp.handle_right = hR + + +def addArrayToMesh(mob, arr): + addVert(mob, arr[0], -1) + mmesh = mob.data + vcounti = len(mmesh.vertices) - 1 + for ai in range(1, len(arr)): + a = arr[ai] + addVert(mob, a, len(mmesh.vertices) - 1) + + +def addMaterial(ob, matname): + mat = bpy.data.materials[matname] + ob.active_material = mat + + +def writeStokeToMesh(arr, jarr, MAINi, HORDERi, TIPSi, orig, gs, rpt=None): + # MAIN BRANCH + print(' WRITING MAIN BRANCH') + llmain = [] + for x in MAINi: + llmain.append(jarr[x]) + mob = writeArrayToMesh('la0MAIN', llmain, gs) + mob.location = orig + + # hORDER BRANCHES + for hOi in range(len(HORDERi)): + print(' WRITING ORDER', hOi) + hO = HORDERi[hOi] + hob = newMesh('la1H' + str(hOi)) + + for y in hO: + llHO = [] + for x in y: + llHO.append(jarr[x]) + addArrayToMesh(hob, llHO) + hob.scale = (gs, gs, gs) + hob.location = orig + + # TIPS + print(' WRITING TIP PATHS') + tob = newMesh('la2TIPS') + for y in TIPSi: + llt = [] + for x in y: + llt.append(jarr[x]) + addArrayToMesh(tob, llt) + tob.scale = (gs, gs, gs) + tob.location = orig + + # ADD MATERIALS TO OBJECTS (IF THEY EXIST) + try: + addMaterial(mob, 'edgeMAT-h0') + addMaterial(hob, 'edgeMAT-h1') + addMaterial(tob, 'edgeMAT-h2') + print(' ADDED MATERIALS') + except: + print(' MATERIALS NOT FOUND') + + # ADD GENERATION REPORT TO ALL MESHES + if rpt: + addReportProp(mob, rpt) + addReportProp(hob, rpt) + addReportProp(tob, rpt) + + +def writeStokeToSingleMesh(arr, jarr, orig, gs, mct, rpt=None): + sgarr = buildCPGraph(arr, mct) + llALL = [] + + Aob = newMesh('laALL') + for pt in jarr: + addVert(Aob, pt) + for cpi in range(len(sgarr)): + ci = sgarr[cpi][0] + pi = sgarr[cpi][1] + addEdge(Aob, pi, ci) + Aob.location = orig + Aob.scale = ((gs, gs, gs)) + + if rpt: + addReportProp(Aob, rpt) + + +def visualizeArray(cg, oob, gs, vm, vs, vc, vv, rst): + # IN: (cellgrid, origin, gridscale, + # mulimesh, single mesh, cubes, voxels, report sting) + origin = oob.location + + # DEAL WITH VERT MULTI-ORIGINS + oct = 2 + if oob.type == 'MESH': + oct = len(oob.data.vertices) + + # JITTER CELLS + if vm or vs: + cjarr = jitterCells(cg, 1) + + if vm: # WRITE ARRAY TO MULTI MESH + + aMi, aHi, aTi = classifyStroke(cg, oct, winmgr.HORDER) + print(':::WRITING TO MULTI-MESH') + writeStokeToMesh(cg, cjarr, aMi, aHi, aTi, origin, gs, rst) + print(':::MULTI-MESH WRITTEN') + + if vs: # WRITE TO SINGLE MESH + print(':::WRITING TO SINGLE MESH') + writeStokeToSingleMesh(cg, cjarr, origin, gs, oct, rst) + print(':::SINGLE MESH WRITTEN') + + if vc: # WRITE ARRAY TO CUBE OBJECTS + print(':::WRITING TO CUBES') + writeArrayToCubes(cg, gs, origin) + print(':::CUBES WRITTEN') + + if vv: # WRITE ARRAY TO VOXEL DATA FILE + print(':::WRITING TO VOXELS') + fname = "FSLGvoxels.raw" + path = os.path.dirname(bpy.data.filepath) + writeArrayToVoxel(cg, path + "\\" + fname) + print(':::VOXEL DATA WRITTEN TO - ', path + "\\" + fname) + + # READ/WRITE ARRAY TO FILE (MIGHT NOT BE NECESSARY) + # tfile = 'c:\\testarr.txt' + # writeArrayToFile(cg, tfile) + # cg = readArrayFromFile(tfile) + + # READ/WRITE ARRAY TO CURVES (OUT OF ORDER) + # writeArrayToCurves('laMAIN', llmain, .10, .25) + + +# ALGORITHM FXNS # +# FROM FALUAM PAPER # +# PLUS SOME STUFF I MADE UP # + +def buildCPGraph(arr, sti=2): + # IN -XYZ ARRAY AS BUILT BY GENERATOR + # OUT -[(CHILDindex, PARENTindex)] + # sti - start index, 2 for Empty, len(me.vertices) for Mesh + sgarr = [] + sgarr.append((1, 0)) + for ai in range(sti, len(arr)): + cs = arr[ai] + cpts = arr[0:ai] + cslap = getStencil3D_26(cs[0], cs[1], cs[2]) + + for nc in cslap: + ct = cpts.count(nc) + if ct > 0: + cti = cpts.index(nc) + sgarr.append((ai, cti)) + return sgarr + + +def buildCPGraph_WORKINPROGRESS(arr, sti=2): + # IN -XYZ ARRAY AS BUILT BY GENERATOR + # OUT -[(CHILDindex, PARENTindex)] + # sti - start index, 2 for Empty, len(me.vertices) for Mesh + sgarr = [] + sgarr.append((1, 0)) + ctix = 0 + for ai in range(sti, len(arr)): + cs = arr[ai] + # cpts = arr[0:ai] + cpts = arr[ctix:ai] + cslap = getStencil3D_26(cs[0], cs[1], cs[2]) + for nc in cslap: + ct = cpts.count(nc) + if ct > 0: + # cti = cpts.index(nc) + cti = ctix + cpts.index(nc) + ctix = cpts.index(nc) + + sgarr.append((ai, cti)) + return sgarr + + +def findChargePath(oc, fc, ngraph, restrict=[], partial=True): + # oc -ORIGIN CHARGE INDEX, fc -FINAL CHARGE INDEX + # ngraph -NODE GRAPH, restrict- INDEX OF SITES CANNOT TRAVERSE + # partial -RETURN PARTIAL PATH IF RESTRICTION ENCOUNTERD + cList = splitList(ngraph, 0) + pList = splitList(ngraph, 1) + aRi = [] + cNODE = fc + for x in range(len(ngraph)): + pNODE = pList[cList.index(cNODE)] + aRi.append(cNODE) + cNODE = pNODE + npNODECOUNT = cList.count(pNODE) + if cNODE == oc: # STOP IF ORIGIN FOUND + aRi.append(cNODE) # RETURN PATH + return aRi + if npNODECOUNT == 0: # STOP IF NO PARENTS + return [] # RETURN [] + if pNODE in restrict: # STOP IF PARENT IS IN RESTRICTION + if partial: # RETURN PARTIAL OR [] + aRi.append(cNODE) + return aRi + else: + return [] + + +def findTips(arr): + lt = [] + for ai in arr[0: len(arr) - 1]: + a = ai[0] + cCOUNT = 0 + for bi in arr: + b = bi[1] + if a == b: + cCOUNT += 1 + if cCOUNT == 0: + lt.append(a) + return lt + + +def findChannelRoots(path, ngraph, restrict=[]): + roots = [] + for ai in range(len(ngraph)): + chi = ngraph[ai][0] + par = ngraph[ai][1] + if par in path and chi not in path and chi not in restrict: + roots.append(par) + droots = deDupe(roots) + return droots + + +def findChannels(roots, tips, ngraph, restrict): + cPATHS = [] + for ri in range(len(roots)): + r = roots[ri] + sL = 1 + sPATHi = [] + for ti in range(len(tips)): + t = tips[ti] + if t < r: + continue + tPATHi = findChargePath(r, t, ngraph, restrict, False) + tL = len(tPATHi) + if tL > sL: + if countChildrenOnPath(tPATHi, ngraph) > 1: + sL = tL + sPATHi = tPATHi + tTEMP = t + tiTEMP = ti + if len(sPATHi) > 0: + print(' found path/idex from', ri, 'of', + len(roots), 'possible | tips:', tTEMP, tiTEMP) + cPATHS.append(sPATHi) + tips.remove(tTEMP) + return cPATHS + + +def findChannels_WORKINPROGRESS(roots, ttips, ngraph, restrict): + cPATHS = [] + tips = list(ttips) + for ri in range(len(roots)): + r = roots[ri] + sL = 1 + sPATHi = [] + tipREMOVE = [] # CHECKED TIP INDEXES, TO BE REMOVED FOR NEXT LOOP + for ti in range(len(tips)): + t = tips[ti] + # print('-CHECKING RT/IDEX:', r, ri, 'AGAINST TIP', t, ti) + # if t < r: continue + if ti < ri: + continue + tPATHi = findChargePath(r, t, ngraph, restrict, False) + tL = len(tPATHi) + if tL > sL: + if countChildrenOnPath(tPATHi, ngraph) > 1: + sL = tL + sPATHi = tPATHi + tTEMP = t + tiTEMP = ti + if tL > 0: + tipREMOVE.append(t) + if len(sPATHi) > 0: + print(' found path from root idex', ri, 'of', + len(roots), 'possible roots | #oftips=', len(tips)) + cPATHS.append(sPATHi) + for q in tipREMOVE: + tips.remove(q) + + return cPATHS + + +def countChildrenOnPath(aPath, ngraph, quick=True): + # RETURN HOW MANY BRANCHES + # COUNT WHEN NODE IS A PARENT >1 TIMES + # quick -STOP AND RETURN AFTER FIRST + cCOUNT = 0 + pList = splitList(ngraph, 1) + for ai in range(len(aPath) - 1): + ap = aPath[ai] + pc = pList.count(ap) + if quick and pc > 1: + return pc + return cCOUNT + + +# CLASSIFY CHANNELS INTO 'MAIN', 'hORDER/SECONDARY' and 'SIDE' +def classifyStroke(sarr, mct, hORDER=1): + print(':::CLASSIFYING STROKE') + # BUILD CHILD/PARENT GRAPH (INDEXES OF sarr) + sgarr = buildCPGraph(sarr, mct) + + # FIND MAIN CHANNEL + print(' finding MAIN') + oCharge = sgarr[0][1] + fCharge = sgarr[len(sgarr) - 1][0] + aMAINi = findChargePath(oCharge, fCharge, sgarr) + + # FIND TIPS + print(' finding TIPS') + aTIPSi = findTips(sgarr) + + # FIND hORDER CHANNEL ROOTS + # hCOUNT = ORDERS BEWTEEN MAIN and SIDE/TIPS + # !!!STILL BUGGY!!! + hRESTRICT = list(aMAINi) # ADD TO THIS AFTER EACH TIME + allHPATHSi = [] # ALL hO PATHS: [[h0], [h1]...] + curPATHSi = [aMAINi] # LIST OF PATHS FIND ROOTS ON + for h in range(hORDER): + allHPATHSi.append([]) + for pi in range(len(curPATHSi)): # LOOP THROUGH ALL PATHS IN THIS ORDER + p = curPATHSi[pi] + # GET ROOTS FOR THIS PATH + aHROOTSi = findChannelRoots(p, sgarr, hRESTRICT) + print(' found', len(aHROOTSi), 'roots in ORDER', h, ':#paths:', len(curPATHSi)) + # GET CHANNELS FOR THESE ROOTS + if len(aHROOTSi) == 0: + print('NO ROOTS FOR FOUND FOR CHANNEL') + aHPATHSi = [] + continue + else: + aHPATHSiD = findChannels(aHROOTSi, aTIPSi, sgarr, hRESTRICT) + aHPATHSi = aHPATHSiD + allHPATHSi[h] += aHPATHSi + # SET THESE CHANNELS AS RESTRICTIONS FOR NEXT ITERATIONS + for hri in aHPATHSi: + hRESTRICT += hri + curPATHSi = aHPATHSi + + # SIDE BRANCHES, FINAL ORDER OF HEIRARCHY + # FROM TIPS THAT ARE NOT IN AN EXISTING PATH + # BACK TO ANY OTHER POINT THAT IS ALREADY ON A PATH + aDRAWNi = [] + aDRAWNi += aMAINi + for oH in allHPATHSi: + for o in oH: + aDRAWNi += o + aTPATHSi = [] + for a in aTIPSi: + if a not in aDRAWNi: + aPATHi = findChargePath(oCharge, a, sgarr, aDRAWNi) + aDRAWNi += aPATHi + aTPATHSi.append(aPATHi) + + return aMAINi, allHPATHSi, aTPATHSi + + +def voxelByVertex(ob, gs): + # 'VOXELIZES' VERTS IN A MESH TO LIST [(x,y,z),(x,y,z)] + # W/ RESPECT GSCALE AND OB ORIGIN (B/C SHOULD BE ORIGIN OBJ) + orig = ob.location + ll = [] + for v in ob.data.vertices: + x = int(v.co.x / gs) + y = int(v.co.y / gs) + z = int(v.co.z / gs) + ll.append((x, y, z)) + return ll + + +def voxelByRays(ob, orig, gs): + # MESH INTO A 3DGRID W/ RESPECT GSCALE AND BOLT ORIGIN + # -DOES NOT TAKE OBJECT ROTATION/SCALE INTO ACCOUNT + # -THIS IS A HORRIBLE, INEFFICIENT FUNCTION + # MAYBE THE RAYCAST/GRID THING ARE A BAD IDEA. BUT I + # HAVE TO 'VOXELIZE THE OBJECT W/ RESCT TO GSCALE/ORIGIN + bbox = ob.bound_box + bbxL = bbox[0][0] + bbxR = bbox[4][0] + bbyL = bbox[0][1] + bbyR = bbox[2][1] + bbzL = bbox[0][2] + bbzR = bbox[1][2] + xct = int((bbxR - bbxL) / gs) + yct = int((bbyR - bbyL) / gs) + zct = int((bbzR - bbzL) / gs) + xs = int(xct / 2) + ys = int(yct / 2) + zs = int(zct / 2) + print(' CASTING', xct, '/', yct, '/', zct, 'cells, total:', xct * yct * zct, 'in obj-', ob.name) + ll = [] + rc = 100 # DISTANCE TO CAST FROM + # RAYCAST TOP/BOTTOM + print(' RAYCASTING TOP/BOTTOM') + for x in range(xct): + for y in range(yct): + xco = bbxL + (x * gs) + yco = bbyL + (y * gs) + v1 = ((xco, yco, rc)) + v2 = ((xco, yco, -rc)) + vz1 = ob.ray_cast(v1, v2) + vz2 = ob.ray_cast(v2, v1) + if vz1[2] != -1: + ll.append((x - xs, y - ys, int(vz1[0][2] * (1 / gs)))) + if vz2[2] != -1: + ll.append((x - xs, y - ys, int(vz2[0][2] * (1 / gs)))) + # RAYCAST FRONT/BACK + print(' RAYCASTING FRONT/BACK') + for x in range(xct): + for z in range(zct): + xco = bbxL + (x * gs) + zco = bbzL + (z * gs) + v1 = ((xco, rc, zco)) + v2 = ((xco, -rc, zco)) + vy1 = ob.ray_cast(v1, v2) + vy2 = ob.ray_cast(v2, v1) + if vy1[2] != -1: + ll.append((x - xs, int(vy1[0][1] * (1 / gs)), z - zs)) + if vy2[2] != -1: + ll.append((x - xs, int(vy2[0][1] * (1 / gs)), z - zs)) + # RAYCAST LEFT/RIGHT + print(' RAYCASTING LEFT/RIGHT') + for y in range(yct): + for z in range(zct): + yco = bbyL + (y * gs) + zco = bbzL + (z * gs) + v1 = ((rc, yco, zco)) + v2 = ((-rc, yco, zco)) + vx1 = ob.ray_cast(v1, v2) + vx2 = ob.ray_cast(v2, v1) + if vx1[2] != -1: + ll.append((int(vx1[0][0] * (1 / gs)), y - ys, z - zs)) + if vx2[2] != -1: + ll.append((int(vx2[0][0] * (1 / gs)), y - ys, z - zs)) + + # ADD IN NEIGHBORS SO BOLT WONT GO THRU + nlist = [] + for l in ll: + nl = getStencil3D_26(l[0], l[1], l[2]) + nlist += nl + + # DEDUPE + print(' ADDED NEIGHBORS, DEDUPING...') + rlist = deDupe(ll + nlist) + qlist = [] + + # RELOCATE GRID W/ RESPECT GSCALE AND BOLT ORIGIN + # !!!NEED TO ADD IN OBJ ROT/SCALE HERE SOMEHOW... + od = Vector( + ((ob.location[0] - orig[0]) / gs, + (ob.location[1] - orig[1]) / gs, + (ob.location[2] - orig[2]) / gs) + ) + for r in rlist: + qlist.append((r[0] + int(od[0]), r[1] + int(od[1]), r[2] + int(od[2]))) + + return qlist + + +def fakeGroundChargePlane(z, charge): + eCL = [] + xy = abs(z) / 2 + eCL += [(0, 0, z, charge)] + eCL += [(xy, 0, z, charge)] + eCL += [(0, xy, z, charge)] + eCL += [(-xy, 0, z, charge)] + eCL += [(0, -xy, z, charge)] + return eCL + + +def addCharges(ll, charge): + # IN: ll - [(x,y,z), (x,y,z)], charge - w + # OUT clist - [(x,y,z,w), (x,y,z,w)] + clist = [] + for l in ll: + clist.append((l[0], l[1], l[2], charge)) + return clist + + +# ALGORITHM FXNS # +# FROM FSLG # + +def getGrowthProbability_KEEPFORREFERENCE(uN, aList): + # IN: uN -USER TERM, cList -CANDIDATE SITES, oList -CANDIDATE SITE CHARGES + # OUT: LIST OF [(XYZ), POT, PROB] + cList = splitList(aList, 0) + oList = splitList(aList, 1) + Omin, Omax = getLowHigh(oList) + if Omin == Omax: + Omax += notZero + Omin -= notZero + PdL = [] + E = 0 + E = notZero # DIVISOR FOR (FSLG - Eqn. 12) + for o in oList: + Uj = (o - Omin) / (Omax - Omin) # (FSLG - Eqn. 13) + E += pow(Uj, uN) + for oi in range(len(oList)): + o = oList[oi] + Ui = (o - Omin) / (Omax - Omin) + Pd = (pow(Ui, uN)) / E # (FSLG - Eqn. 12) + PdINT = Pd * 100 + PdL.append(Pd) + return PdL + + +# WORK IN PROGRESS, TRYING TO SPEED THESE UP +def fslg_e13(x, min, max, u): + return pow((x - min) / (max - min), u) + + +def addit(x, y): + return x + y + + +def fslg_e12(x, min, max, u, e): + return (fslg_e13(x, min, max, u) / e) * 100 + + +def getGrowthProbability(uN, aList): + # IN: uN -USER TERM, cList -CANDIDATE SITES, oList -CANDIDATE SITE CHARGES + # OUT: LIST OF PROB + cList = splitList(aList, 0) + oList = splitList(aList, 1) + Omin, Omax = getLowHigh(oList) + if Omin == Omax: + Omax += notZero + Omin -= notZero + PdL = [] + E = notZero + minL = [Omin for q in range(len(oList))] + maxL = [Omax for q in range(len(oList))] + uNL = [uN for q in range(len(oList))] + E = sum(map(fslg_e13, oList, minL, maxL, uNL)) + EL = [E for q in range(len(oList))] + mp = map(fslg_e12, oList, minL, maxL, uNL, EL) + for m in mp: + PdL.append(m) + return PdL + + +def updatePointCharges(p, cList, eList=[]): + # IN: pNew -NEW GROWTH CELL + # cList -OLD CANDIDATE SITES, eList -SAME + # OUT: LIST OF NEW CHARGE AT CANDIDATE SITES + r1 = 1 / 2 # (FSLG - Eqn. 10) + nOiL = [] + for oi in range(len(cList)): + o = cList[oi][1] + c = cList[oi][0] + iOe = 0 + rit = dist(c[0], c[1], c[2], p[0], p[1], p[2]) + iOe += (1 - (r1 / rit)) + Oit = o + iOe + nOiL.append((c, Oit)) + return nOiL + + +def initialPointCharges(pList, cList, eList=[]): + # IN: p -CHARGED CELL (XYZ), cList -CANDIDATE SITES (XYZ, POT, PROB) + # OUT: cList -WITH POTENTIAL CALCULATED + r1 = 1 / 2 # (FSLG - Eqn. 10) + npList = [] + for p in pList: + npList.append(((p[0], p[1], p[2]), 1.0)) + for e in eList: + npList.append(((e[0], e[1], e[2]), e[3])) + OiL = [] + for i in cList: + Oi = 0 + for j in npList: + if i != j[0]: + rij = dist(i[0], i[1], i[2], j[0][0], j[0][1], j[0][2]) + Oi += (1 - (r1 / rij)) * j[1] # CHARGE INFLUENCE + OiL.append(((i[0], i[1], i[2]), Oi)) + return OiL + + +def getCandidateSites(aList, iList=[]): + # IN: aList -(X,Y,Z) OF CHARGED CELL SITES, iList -insulator sites + # OUT: CANDIDATE LIST OF GROWTH SITES [(X,Y,Z)] + tt1 = time.clock() + cList = [] + for c in aList: + tempList = getStencil3D_26(c[0], c[1], c[2]) + for t in tempList: + if t not in aList and t not in iList: + cList.append(t) + ncList = deDupe(cList) + tt2 = time.clock() + + return ncList + + +# SETUP FXNS # + +def setupObjects(): + oOB = bpy.data.objects.new('ELorigin', None) + oOB.location = ((0, 0, 10)) + bpy.context.scene.objects.link(oOB) + + gOB = bpy.data.objects.new('ELground', None) + gOB.empty_draw_type = 'ARROWS' + bpy.context.scene.objects.link(gOB) + + cME = makeMeshCube(1) + cOB = bpy.data.objects.new('ELcloud', cME) + cOB.location = ((-2, 8, 12)) + cOB.hide_render = True + bpy.context.scene.objects.link(cOB) + + iME = makeMeshCube(1) + for v in iME.vertices: + xyl = 6.5 + zl = .5 + v.co[0] = v.co[0] * xyl + v.co[1] = v.co[1] * xyl + v.co[2] = v.co[2] * zl + iOB = bpy.data.objects.new('ELinsulator', iME) + iOB.location = ((0, 0, 5)) + iOB.hide_render = True + bpy.context.scene.objects.link(iOB) + + try: + winmgr.OOB = 'ELorigin' + winmgr.GOB = 'ELground' + winmgr.COB = 'ELcloud' + winmgr.IOB = 'ELinsulator' + except: + pass + + +def checkSettings(): + check = True + if winmgr.OOB == "": + print('ERROR: NO ORIGIN OBJECT SELECTED') + check = False + if winmgr.GROUNDBOOL and winmgr.GOB == "": + print('ERROR: NO GROUND OBJECT SELECTED') + check = False + if winmgr.CLOUDBOOL and winmgr.COB == "": + print('ERROR: NO CLOUD OBJECT SELECTED') + check = False + if winmgr.IBOOL and winmgr.IOB == "": + print('ERROR: NO INSULATOR OBJECT SELECTED') + check = False + # should make a popup here + return check + + +# MAIN # + +def FSLG(): + # FAST SIMULATION OF LAPLACIAN GROWTH # + print('\n<<<<<<------GO GO GADGET: FAST SIMULATION OF LAPLACIAN GROWTH!') + tc1 = time.clock() + TSTEPS = winmgr.TSTEPS + + obORIGIN = bpy.context.scene.objects[winmgr.OOB] + obGROUND = bpy.context.scene.objects[winmgr.GOB] + winmgr.ORIGIN = obORIGIN.location + winmgr.GROUNDZ = int((obGROUND.location[2] - winmgr.ORIGIN[2]) / winmgr.GSCALE) + + # 1) INSERT INTIAL CHARGE(S) POINT (USES VERTS IF MESH) + cgrid = [(0, 0, 0)] + if obORIGIN.type == 'MESH': + print("<<<<<<------ORIGIN OBJECT IS MESH, 'VOXELIZING' INTIAL CHARGES FROM VERTS") + cgrid = voxelByVertex(obORIGIN, winmgr.GSCALE) + if winmgr.VMMESH: + print("<<<<<<------CANNOT CLASSIFY STROKE FROM VERT ORIGINS YET, NO MULTI-MESH OUTPUT") + winmgr.VMMESH = False + winmgr.VSMESH = True + + # GROUND CHARGE CELL / INSULATOR LISTS (eChargeList/icList) + eChargeList = [] + icList = [] + if winmgr.GROUNDBOOL: + eChargeList = fakeGroundChargePlane(winmgr.GROUNDZ, winmgr.GROUNDC) + if winmgr.CLOUDBOOL: + print("<<<<<<------'VOXELIZING' CLOUD OBJECT (COULD TAKE SOME TIME)") + obCLOUD = bpy.context.scene.objects[winmgr.COB] + eChargeListQ = voxelByRays(obCLOUD, winmgr.ORIGIN, winmgr.GSCALE) + eChargeList = addCharges(eChargeListQ, winmgr.CLOUDC) + print('<<<<<<------CLOUD OBJECT CELL COUNT = ', len(eChargeList)) + if winmgr.IBOOL: + print("<<<<<<------'VOXELIZING' INSULATOR OBJECT (COULD TAKE SOME TIME)") + obINSULATOR = bpy.context.scene.objects[winmgr.IOB] + icList = voxelByRays(obINSULATOR, winmgr.ORIGIN, winmgr.GSCALE) + print('<<<<<<------INSULATOR OBJECT CELL COUNT = ', len(icList)) + + # 2) LOCATE CANDIDATE SITES AROUND CHARGE + cSites = getCandidateSites(cgrid, icList) + + # 3) CALC POTENTIAL AT EACH SITE (Eqn. 10) + cSites = initialPointCharges(cgrid, cSites, eChargeList) + + ts = 1 + while ts <= TSTEPS: + # 1) SELECT NEW GROWTH SITE (Eqn. 12) + # GET PROBABILITIES AT CANDIDATE SITES + gProbs = getGrowthProbability(winmgr.BIGVAR, cSites) + # CHOOSE NEW GROWTH SITE BASED ON PROBABILITIES + gSitei = weightedRandomChoice(gProbs) + gsite = cSites[gSitei][0] + + # 2) ADD NEW POINT CHARGE AT GROWTH SITE + # ADD NEW GROWTH CELL TO GRID + cgrid.append(gsite) + # REMOVE NEW GROWTH CELL FROM CANDIDATE SITES + cSites.remove(cSites[gSitei]) + + # 3) UPDATE POTENTIAL AT CANDIDATE SITES (Eqn. 11) + cSites = updatePointCharges(gsite, cSites, eChargeList) + + # 4) ADD NEW CANDIDATES SURROUNDING GROWTH SITE + # GET CANDIDATE 'STENCIL' + ncSitesT = getCandidateSites([gsite], icList) + # REMOVE CANDIDATES ALREADY IN CANDIDATE LIST OR CHARGE GRID + ncSites = [] + cSplit = splitList(cSites, 0) + for cn in ncSitesT: + if cn not in cSplit and cn not in cgrid: + ncSites.append((cn, 0)) + + # 5) CALC POTENTIAL AT NEW CANDIDATE SITES (Eqn. 10) + ncSplit = splitList(ncSites, 0) + ncSites = initialPointCharges(cgrid, ncSplit, eChargeList) + + # ADD NEW CANDIDATE SITES TO CANDIDATE LIST + for ncs in ncSites: + cSites.append(ncs) + + # ITERATION COMPLETE + istr1 = ':::T-STEP: ' + str(ts) + '/' + str(TSTEPS) + istr12 = ' | GROUNDZ: ' + str(winmgr.GROUNDZ) + ' | ' + istr2 = 'CANDS: ' + str(len(cSites)) + ' | ' + istr3 = 'GSITE: ' + str(gsite) + print(istr1 + istr12 + istr2 + istr3) + ts += 1 + + # EARLY TERMINATION FOR GROUND/CLOUD STRIKE + if winmgr.GROUNDBOOL: + if gsite[2] == winmgr.GROUNDZ: + ts = TSTEPS + 1 + print('<<<<<<------EARLY TERMINATION DUE TO GROUNDSTRIKE') + continue + if winmgr.CLOUDBOOL: + if gsite in splitListCo(eChargeList): + ts = TSTEPS + 1 + print('<<<<<<------EARLY TERMINATION DUE TO CLOUDSTRIKE') + continue + + tc2 = time.clock() + tcRUN = tc2 - tc1 + print('<<<<<<------LAPLACIAN GROWTH LOOP COMPLETED: ' + str(len(cgrid)) + ' / ' + str(tcRUN)[0:5] + ' SECONDS') + print('<<<<<<------VISUALIZING DATA') + + reportSTRING = getReportString(tcRUN) + # VISUALIZE ARRAY + visualizeArray( + cgrid, obORIGIN, winmgr.GSCALE, + winmgr.VMMESH, winmgr.VSMESH, + winmgr.VCUBE, winmgr.VVOX, reportSTRING + ) + print('<<<<<<------COMPLETE') + + +# GUI # + +# NOT IN UI +bpy.types.WindowManager.ORIGIN = bpy.props.FloatVectorProperty(name="origin charge") +bpy.types.WindowManager.GROUNDZ = bpy.props.IntProperty(name="ground Z coordinate") +bpy.types.WindowManager.HORDER = bpy.props.IntProperty(name="secondary paths orders") +# IN UI +bpy.types.WindowManager.TSTEPS = bpy.props.IntProperty( + name="iterations", + description="number of cells to create, will end early if hits ground plane or cloud") +bpy.types.WindowManager.GSCALE = bpy.props.FloatProperty( + name="grid unit size", + description="scale of cells, .25 = 4 cells per blenderUnit") +bpy.types.WindowManager.BIGVAR = bpy.props.FloatProperty( + name="straightness", + description="straightness/branchiness of bolt, <2 is mush, >12 is staight line, 6.3 is good") +bpy.types.WindowManager.GROUNDBOOL = bpy.props.BoolProperty( + name="use ground object", description="use ground plane or not") +bpy.types.WindowManager.GROUNDC = bpy.props.IntProperty( + name="ground charge", description="charge of ground plane") +bpy.types.WindowManager.CLOUDBOOL = bpy.props.BoolProperty( + name="use cloud object", + description="use cloud obj, attracts and terminates like ground but " + "any obj instead of z plane, can slow down loop if obj is large, overrides ground") +bpy.types.WindowManager.CLOUDC = bpy.props.IntProperty( + name="cloud charge", + description="charge of a cell in cloud object (so total charge also depends on obj size)") + +bpy.types.WindowManager.VMMESH = bpy.props.BoolProperty( + name="multi mesh", + description="output to multi-meshes for different materials on main/sec/side branches") +bpy.types.WindowManager.VSMESH = bpy.props.BoolProperty( + name="single mesh", + description="output to single mesh for using build modifier and particles for effects") +bpy.types.WindowManager.VCUBE = bpy.props.BoolProperty( + name="cubes", description="CTRL-J after run to JOIN, outputs a bunch of cube objest, mostly for testing") +bpy.types.WindowManager.VVOX = bpy.props.BoolProperty( + name="voxel (experimental)", + description="output to a voxel file to bpy.data.filepath\FSLGvoxels.raw - doesn't work well right now") +bpy.types.WindowManager.IBOOL = bpy.props.BoolProperty( + name="use insulator object", description="use insulator mesh object to prevent growth of bolt in areas") +bpy.types.WindowManager.OOB = bpy.props.StringProperty( + description="origin of bolt, can be an Empty, if obj is mesh will use all verts as charges") +bpy.types.WindowManager.GOB = bpy.props.StringProperty( + description="object to use as ground plane, uses z coord only") +bpy.types.WindowManager.COB = bpy.props.StringProperty( + description="object to use as cloud, best to use a cube") +bpy.types.WindowManager.IOB = bpy.props.StringProperty( + description="object to use as insulator, 'voxelized' before generating bolt, can be slow") + +# DEFAULT USER SETTINGS +winmgr.TSTEPS = 350 +winmgr.HORDER = 1 +winmgr.GSCALE = 0.12 +winmgr.BIGVAR = 6.3 +winmgr.GROUNDBOOL = True +winmgr.GROUNDC = -250 +winmgr.CLOUDBOOL = False +winmgr.CLOUDC = -1 +winmgr.VMMESH = True +winmgr.VSMESH = False +winmgr.VCUBE = False +winmgr.VVOX = False +winmgr.IBOOL = False +try: + winmgr.OOB = "ELorigin" + winmgr.GOB = "ELground" + winmgr.COB = "ELcloud" + winmgr.IOB = "ELinsulator" +except: + pass + +# TESTING USER SETTINGS +if False: + winmgr.TSTEPS = 40 + winmgr.GROUNDBOOL = True + winmgr.CLOUDBOOL = True + winmgr.IBOOL = True + + +class runFSLGLoopOperator(bpy.types.Operator): + '''By The Mighty Hammer Of Thor!!!''' + bl_idname = "object.runfslg_operator" + bl_label = "run FSLG Loop Operator" + + def execute(self, context): + if checkSettings(): + FSLG() + else: + pass + return {'FINISHED'} + + +class setupObjectsOperator(bpy.types.Operator): + '''create origin/ground/cloud/insulator objects''' + bl_idname = "object.setup_objects_operator" + bl_label = "Setup Objects Operator" + + def execute(self, context): + setupObjects() + return {'FINISHED'} + + +class OBJECT_PT_fslg(bpy.types.Panel): + bl_label = "Laplacian Lightning" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_context = "objectmode" + bl_category = "Create" + bl_options = {'DEFAULT_CLOSED'} + + def draw(self, context): + layout = self.layout + colR = layout.column() + colR.label('-for progress open console-') + colR.label('Help > Toggle System Console') + colR.prop(winmgr, 'TSTEPS') + colR.prop(winmgr, 'GSCALE') + colR.prop(winmgr, 'BIGVAR') + colR.operator('object.setup_objects_operator', text='create setup objects') + colR.label('origin object') + colR.prop_search(winmgr, "OOB", context.scene, "objects") + colR.prop(winmgr, 'GROUNDBOOL') + colR.prop_search(winmgr, "GOB", context.scene, "objects") + colR.prop(winmgr, 'GROUNDC') + colR.prop(winmgr, 'CLOUDBOOL') + colR.prop_search(winmgr, "COB", context.scene, "objects") + colR.prop(winmgr, 'CLOUDC') + colR.prop(winmgr, 'IBOOL') + colR.prop_search(winmgr, "IOB", context.scene, "objects") + colR.operator('object.runfslg_operator', text='generate lightning') + colR.prop(winmgr, 'VMMESH') + colR.prop(winmgr, 'VSMESH') + colR.prop(winmgr, 'VCUBE') + + +def getReportString(rtime): + rSTRING1 = 't:' + str(winmgr.TSTEPS) + ',sc:' + str(winmgr.GSCALE)[0:4] + ',uv:' + str(winmgr.BIGVAR)[0:4] + ',' + rSTRING2 = 'ori:' + str(winmgr. ORIGIN[0]) + '/' + str(winmgr. ORIGIN[1]) + '/' + str(winmgr. ORIGIN[2]) + ',' + rSTRING3 = 'gz:' + str(winmgr.GROUNDZ) + ',gc:' + str(winmgr.GROUNDC) + ',rtime:' + str(int(rtime)) + return rSTRING1 + rSTRING2 + rSTRING3 + + +def addReportProp(ob, str): + bpy.types.Object.FSLG_REPORT = bpy.props.StringProperty( + name='fslg_report', default='') + ob.FSLG_REPORT = str + + +def register(): + bpy.utils.register_class(runFSLGLoopOperator) + bpy.utils.register_class(setupObjectsOperator) + bpy.utils.register_class(OBJECT_PT_fslg) + + +def unregister(): + bpy.utils.unregister_class(runFSLGLoopOperator) + bpy.utils.unregister_class(setupObjectsOperator) + bpy.utils.unregister_class(OBJECT_PT_fslg) + + +if __name__ == "__main__": + # RUN FOR TESTING + # FSLG() + + # UI + register() + pass + + +# FXN BENCHMARKS # + +def BENCH(): + print('\n\n\n--->BEGIN BENCHMARK') + bt0 = time.clock() + # MAKE A BIG LIST + tsize = 25 + tlist = [] + for x in range(tsize): + for y in range(tsize): + for z in range(tsize): + tlist.append((x, y, z)) + tlist.append((x, y, z)) + + # FUNCTION TO TEST + bt1 = time.clock() + # print('LENS - ', len(tlist), len(ll)) + + bt2 = time.clock() + btRUNb = bt2 - bt1 + btRUNa = bt1 - bt0 + print('--->SETUP TIME : ', btRUNa) + print('--->BENCHMARK TIME: ', btRUNb) + print('--->GRIDSIZE: ', tsize, ' - ', tsize * tsize * tsize) diff --git a/add_advanced_objects/object_mangle_tools.py b/add_advanced_objects/object_mangle_tools.py new file mode 100644 index 00000000..81110ad3 --- /dev/null +++ b/add_advanced_objects/object_mangle_tools.py @@ -0,0 +1,211 @@ +# mangle_tools.py (c) 2011 Phil Cote (cotejrp1) +# +# ***** BEGIN GPL LICENSE BLOCK ***** +# +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ***** END GPL LICENCE BLOCK ***** + +bl_info = { + "name": "Mangle Tools", + "author": "Phil Cote", + "version": (0, 2), + "blender": (2, 71, 0), + "location": "View3D > Toolshelf > Tools Tab", + "description": "Set of tools to mangle curves, meshes, and shape keys", + "warning": "", # used for warning icon and text in addons panel + "wiki_url": "", + "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/", + "category": "Object"} + + +import bpy +import random +import time +from math import pi +import bmesh + +def move_coordinate(context, co, is_curve=False): + xyz_const = context.scene.constraint_vector + random.seed(time.time()) + multiplier = 1 + + # For curves, we base the multiplier on the circumference formula. + # This helps make curve changes more noticable. + if is_curve: + multiplier = 2 * pi + random_mag = context.scene.random_magnitude + if xyz_const[0]: + co.x += .01 * random.randrange( -random_mag, random_mag ) * multiplier + if xyz_const[1]: + co.y += .01 * random.randrange( -random_mag, random_mag ) * multiplier + if xyz_const[2]: + co.z += .01 * random.randrange( -random_mag, random_mag ) * multiplier + + +class MeshManglerOperator(bpy.types.Operator): + """Push vertices on the selected object around in random """ \ + """directions to create a crumpled look""" + bl_idname = "ba.mesh_mangler" + bl_label = "Mangle Mesh" + bl_options = { "REGISTER", "UNDO" } + + @classmethod + def poll(cls, context): + ob = context.active_object + return ob != None and ob.type == 'MESH' + + def execute(self, context): + mesh = context.active_object.data + bm = bmesh.new() + bm.from_mesh(mesh) + verts, faces = bm.verts, bm.faces + randomMag = context.scene.random_magnitude + random.seed( time.time() ) + + if mesh.shape_keys != None: + self.report({'INFO'}, "Cannot mangle mesh: Shape keys present") + return {'CANCELLED'} + + for vert in verts: + xVal = .01 * random.randrange( -randomMag, randomMag ) + yVal = .01 * random.randrange( -randomMag, randomMag) + zVal = .01 * random.randrange( -randomMag, randomMag ) + vert.co.x = vert.co.x + xVal + vert.co.y = vert.co.y + yVal + vert.co.z = vert.co.z + zVal + + bm.to_mesh(mesh) + mesh.update() + return {'FINISHED'} + + +class AnimanglerOperator(bpy.types.Operator): + """Make a shape key and pushes the verts around on it """ \ + """to set up for random pulsating animation""" + bl_idname = "ba.ani_mangler" + bl_label = "Mangle Shape Key" + + + @classmethod + def poll(cls, context): + ob = context.active_object + return ob != None and ob.type in [ 'MESH', 'CURVE' ] + + def execute(self, context): + scn = context.scene + mangleName = scn.mangle_name + ob = context.object + shapeKey = ob.shape_key_add( name=mangleName ) + verts = shapeKey.data + + for vert in verts: + move_coordinate(context, vert.co, is_curve=ob.type=='CURVE') + + return {'FINISHED'} + + +class CurveManglerOp(bpy.types.Operator): + """Mangle a curve to the degree the user specifies""" + bl_idname = "ba.curve_mangler" + bl_label = "Mangle Curve" + bl_options = { 'REGISTER', 'UNDO' } + + @classmethod + def poll(cls, context): + ob = context.active_object + return ob != None and ob.type == "CURVE" + + + def execute(self, context): + + ob = context.active_object + if ob.data.shape_keys != None: + self.report({'INFO'}, "Cannot mangle curve. Shape keys present") + return {'CANCELLED'} + splines = context.object.data.splines + + for spline in splines: + if spline.type == 'BEZIER': + points = spline.bezier_points + elif spline.type in {'POLY', 'NURBS'}: + points = spline.points + + for point in points: + move_coordinate(context, point.co, is_curve=True) + + return {'FINISHED'} + + +class MangleToolsPanel(bpy.types.Panel): + bl_label = "Mangle Tools" + bl_space_type = "VIEW_3D" + bl_context = "objectmode" + bl_region_type="TOOLS" + bl_category = "Create" + bl_options = {'DEFAULT_CLOSED'} + + + def draw(self, context): + scn = context.scene + obj = context.object + if obj.type in ['MESH',]: + layout = self.layout + col = layout.column() + col.prop(scn, "constraint_vector") + col.prop(scn, "random_magnitude") + col.operator("ba.mesh_mangler") + col.separator() + col.prop(scn, "mangle_name") + col.operator("ba.ani_mangler") + else: + layout = self.layout + col = layout.column() + col.label("Please Select Mesh Object") + +IntProperty = bpy.props.IntProperty +StringProperty = bpy.props.StringProperty +BoolVectorProperty = bpy.props.BoolVectorProperty + +def register(): + bpy.utils.register_class(AnimanglerOperator) + bpy.utils.register_class(MeshManglerOperator) + bpy.utils.register_class(CurveManglerOp) + bpy.utils.register_class(MangleToolsPanel) + scnType = bpy.types.Scene + + + scnType.constraint_vector = BoolVectorProperty(name="Mangle Constraint", + default=(True,True,True), + subtype='XYZ', + description="Constrains Mangle Direction") + + scnType.random_magnitude = IntProperty( name = "Mangle Severity", + default = 5, min = 1, max = 30, + description = "Severity of mangling") + + scnType.mangle_name = StringProperty(name="Shape Key Name", + default="mangle", + description="Name given for mangled shape keys") +def unregister(): + bpy.utils.unregister_class(AnimanglerOperator) + bpy.utils.unregister_class(MeshManglerOperator) + bpy.utils.unregister_class(MangleToolsPanel) + bpy.utils.unregister_class(CurveManglerOp) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/oscurart_chain_maker.py b/add_advanced_objects/oscurart_chain_maker.py new file mode 100644 index 00000000..c336e44d --- /dev/null +++ b/add_advanced_objects/oscurart_chain_maker.py @@ -0,0 +1,280 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# TODO: translate the comments into English + +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", + "tracker_url": "", + "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("EslabonCero" + 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['EslabonCero' + str(hueso.name)].scale = (hueso.length * mult, + hueso.length * mult, + hueso.length * mult) + # Parent Objects + bpy.data.objects['EslabonCero' + str(hueso.name)].parent = ARMATURE + bpy.data.objects['EslabonCero' + str(hueso.name)].parent_type = 'BONE' + bpy.data.objects['EslabonCero' + 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("EslabonNov" + 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['EslabonNov' + str(hueso.name)].scale = (hueso.length * mult, + hueso.length * mult, + hueso.length * mult) + # Parent objects + bpy.data.objects['EslabonNov' + str(hueso.name)].parent = ARMATURE + bpy.data.objects['EslabonNov' + str(hueso.name)].parent_type = 'BONE' + bpy.data.objects['EslabonNov' + 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): + makeChain(self, context, self.multiplier, self.curverig) + 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/pixelate_3d.py b/add_advanced_objects/pixelate_3d.py new file mode 100644 index 00000000..90e129da --- /dev/null +++ b/add_advanced_objects/pixelate_3d.py @@ -0,0 +1,104 @@ +####################################################### +# very simple 'pixelization' or 'voxelization' engine # +####################################################### + +bl_info = { + "name": "3D Pix", + "author": "liero", + "version": (0, 5, 1), + "blender": (2, 74, 0), + "location": "View3D > Tool Shelf", + "description": "Creates a 3d pixelated version of the object.", + "category": "Object"} + +import bpy +import mathutils +from mathutils import Vector + +bpy.types.WindowManager.size = bpy.props.FloatProperty(name='Size', min=.05, max=5, default=.25, description='Size of the cube / grid') +bpy.types.WindowManager.gap = bpy.props.IntProperty(name='Gap', min=0, max=90, default=10, subtype='PERCENTAGE', description='Separation - percent of size') +bpy.types.WindowManager.smooth = bpy.props.FloatProperty(name='Smooth', min=0, max=1, default=.0, description='Smooth factor when subdividing mesh') + + +def pix(obj): + sce = bpy.context.scene + wm = bpy.context.window_manager + 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 > wm.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=wm.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]) % wm.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 = wm.size * (100 - wm.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(bpy.types.Operator): + bl_idname = 'object.pixelate' + bl_label = 'Pixelate Object' + bl_description = 'Create a 3d pixelated version of the 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 + + column = layout.column(align=True) + column.prop(context.window_manager, "size") + column.prop(context.window_manager, "gap") + layout.prop(context.window_manager, "smooth") + + def execute(self, context): + objeto = bpy.context.object + pix(objeto) + return {'FINISHED'} + +classes = ( + Pixelate, +) + +def register(): + for cls in classes: + bpy.utils.register_class(cls) + + +def unregister(): + for cls in classes: + bpy.utils.unregister_class(cls) + +if __name__ == '__main__': + register() diff --git a/add_advanced_objects/random_box_structure.py b/add_advanced_objects/random_box_structure.py new file mode 100644 index 00000000..5440b8b0 --- /dev/null +++ b/add_advanced_objects/random_box_structure.py @@ -0,0 +1,193 @@ +bl_info = { + "name": "Add Random Box Structure", + "author": "Dannyboy", + "version": (1, 0), + "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_options = {'REGISTER', 'UNDO'} + + dc = BoolProperty( + name="Delete Base Mesh(s)?", + default=True + ) + wh = BoolProperty( + name="Stay Within Base Mesh(s)?", + 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 + ) + + 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'} + + @classmethod + def poll(cls, context): + ob = context.active_object + return ob is not None and ob.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 add_object_button(self, context): + self.layout.operator(makestructure.bl_idname, text="Add Random Box structure", icon='PLUGIN') + + +def register(): + bpy.utils.register_class(makestructure) + bpy.types.INFO_MT_add.append(add_object_button) + + +def unregister(): + bpy.utils.unregister_class(makestructure) + bpy.types.INFO_MT_add.remove(add_object_button) + + +if __name__ == "__main__": + register() diff --git a/add_advanced_objects/rope_alpha.py b/add_advanced_objects/rope_alpha.py new file mode 100644 index 00000000..f0406148 --- /dev/null +++ b/add_advanced_objects/rope_alpha.py @@ -0,0 +1,762 @@ +# 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 : translate comments, 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), + "blender": (2, 7, 3), + "location": "Left Toolbar > ClothRope", + "warning": "", + "wiki_url": "", + "tracker_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 == "EDIT" or bpy.context.mode == "EDIT_CURVE" or bpy.context.mode == "EDIT_MESH": + bpy.ops.object.mode_set(mode='OBJECT') + +# Clear scene: + + +def reset_scene(): + desocultar("todo") + # el play back al principio + bpy.ops.screen.frame_jump(end=False) + try: + salir_de_editmode() + except: + pass + area = bpy.context.area + # en el outliner expando todo para poder seleccionar los emptys hijos + old_type = area.type + area.type = 'OUTLINER' + bpy.ops.outliner.expanded_toggle() + area.type = old_type + # vuelvo al contexto donde estaba + seleccionar_todo() + bpy.ops.object.delete(use_global=False) + + +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() + # bpy.ops.mesh.select_all(action="SELECT") + + +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("el vertice " + str(v.index) + " esta seleccionado") + + +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): + # scn = bpy.context.scene + 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 tab_editmode(): + bpy.ops.object.editmode_toggle() + + +def obtener_coords_vertex_seleccionados(): + coordenadas_de_vertices = [] + for ob in bpy.context.selected_objects: + print(ob.name) + 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" + + ropelenght = IntProperty( + name="longitud", + default=5 + ) + ropesegments = IntProperty( + name="rsegments", + default=5 + ) + qcr = IntProperty( + name="qualcolr", + min=1, max=20, + default=20 + ) + substeps = IntProperty( + name="rsubsteps", + min=4, max=80, + default=50 + ) + resrope = IntProperty( + name="resr", + default=5 + ) + radiusrope = FloatProperty( + name="radius", + min=0.04, max=1, + default=0.04 + ) + hide_emptys = BoolProperty( + name="hemptys", + default=False + ) + + def execute(self, context): + # add new scene + bpy.ops.scene.new(type="NEW") + scene = bpy.context.scene + scene.name = "Test Rope" + seleccionar_todo() + longitud = self.ropelenght + # para que desde el primer punto hasta el ultimo, entre + # medias tenga x segmentos debo sumarle 1 a la cantidad: + cuantos_segmentos = self.ropesegments + 1 + calidad_de_colision = self.qcr + substeps = self.substeps + deseleccionar_todo() + # creamos el empty que sera el padre de todo + 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" + deseleccionar_todo() + # creamos un plano y lo borramos + 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] + # renombrar: + ob.name = "cuerda" + entrar_en_editmode() # entramos en edit mode + select_all_in_edit_mode(ob) + # seleccionar_todo() # ya viene por default seleccionado + borrar_elementos_seleccionados("vertices") + salir_de_editmode() # salimos de edit mode + crear_vertices(ob) # creamos un vertex + # creando el grupo Group para el PIN + # Group contiene los vertices del pin y Group.001 contiene la linea unica principal + entrar_en_editmode() # entramos en edit mode + bpy.ops.object.vertex_group_add() # creamos un grupo + select_all_in_edit_mode(ob) + bpy.ops.object.vertex_group_assign() # y lo asignamos + # los hooks van a la curva no a la guia poligonal... + # creo el primer hook sin necesidad de crear luego el locator a mano: + # bpy.ops.object.hook_add_newob() + salir_de_editmode() # salimos de edit mode + ob.vertex_groups[0].name = "Pin" + deseleccionar_todo() + seleccionar_por_nombre("cuerda") + # hago los extrudes del vertice: + for i in range(cuantos_segmentos): + entrar_en_editmode() + extruir_vertices(longitud, cuantos_segmentos) + # y los ELIMINO del grupo PIN + bpy.ops.object.vertex_group_remove_from() + # obtengo la direccion para lego crear el locator en su posicion + pos = obtener_coords_vertex_seleccionados() + # los hooks van a la curva no a la guia poligonal... + # creo el hook sin necesidad de crear el locator a mano: + # bpy.ops.object.hook_add_newob() + salir_de_editmode() # salimos de edit mode + # creo el locator en su sitio + crear_locator(pos) + deseleccionar_todo() + seleccionar_por_nombre("cuerda") + deseleccionar_todo() + seleccionar_por_nombre("cuerda") + # vuelvo a seleccionar la cuerda + entrar_en_editmode() + pos = obtener_coords_vertex_seleccionados() # y obtenemos su posicion + salir_de_editmode() + # creamos el ultimo locator + crear_locator(pos) + deseleccionar_todo() + seleccionar_por_nombre("cuerda") + entrar_en_editmode() # entramos en edit mode + bpy.ops.object.vertex_group_add() # CREANDO GRUPO GUIA MAESTRA + select_all_in_edit_mode(ob) + bpy.ops.object.vertex_group_assign() # y lo asignamos + ob.vertex_groups[1].name = "Guide_rope" + # extruimos la curva para que tenga un minimo grosor para colisionar + 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 + # DUPLICAMOS para convertir a curva: + # selecciono los vertices que forman parte del grupo Group.001 + seleccionar_por_nombre("cuerda") + 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 + entrar_en_editmode() + # ya tenemos la guia seleccionada: + # la duplicamos: + 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 + } + ) + # separamos por seleccion: + bpy.ops.mesh.separate(type='SELECTED') + salir_de_editmode() + deseleccionar_todo() + seleccionar_por_nombre("cuerda.001") + # a la nueva curva copiada le quitamos el cloth: + bpy.ops.object.modifier_remove(modifier="Cloth") + # la convertimos en curva: + bpy.ops.object.convert(target='CURVE') + # todos los emptys: + emptys = [] + for eo in bpy.data.objects: + if eo.type == 'EMPTY': + if eo.name != "Rope": + emptys.append(eo) + # print(emptys) + # cuantos puntos tiene la becier: + # len(bpy.data.objects['cuerda.001'].data.splines[0].points) + # seleccionar y deseleccionar: + bc = bpy.data.objects['cuerda.001'] + 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 + # entrar_en_editmode() + ob = bpy.data.objects['cuerda'] + 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() + # deselect_all_in_edit_mode(ob) + salir_de_editmode() + n = n + 1 + + # ocultar los emptys: + # for e in emptys: + deseleccionar_todo() + # emparentando todo al empty esferico: + seleccionar_por_nombre("cuerda.001") + seleccionar_por_nombre("cuerda") + seleccionar_por_nombre("Rope") + bpy.ops.object.parent_set(type='OBJECT', keep_transform=True) + deseleccionar_todo() + # display que no muestre las relaciones + ocultar_relationships() + seleccionar_por_nombre("cuerda.001") + # cuerda curva 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=310) + + def draw(self, context): + layout = self.layout + box = layout.box() + col = box.column() + 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 = "Rope Ball" + + # defaults rope ball + ropelenght2 = IntProperty( + name="longitud", + default=10 + ) + ropesegments2 = IntProperty( + name="rsegments", + min=0, max=999, + default=6 + ) + radiuscubes = FloatProperty( + name="radius", + default=0.5 + ) + radiusrope = FloatProperty( + name="radius", + default=0.4 + ) + worldsteps = IntProperty( + name="worldsteps", + min=60, max=1000, + default=250 + ) + solveriterations = IntProperty( + name="solveriterations", + min=10, max=100, + default=50 + ) + massball = IntProperty( + name="massball", + default=1 + ) + resrope = IntProperty( + name="resolucion", + default=4 + ) + grados = FloatProperty( + name="grados", + default=45 + ) + separacion = FloatProperty( + name="separacion", + default=0.1 + ) + hidecubes = BoolProperty( + name="hidecubes", + default=False + ) + + def execute(self, context): + world_steps = self.worldsteps + solver_iterations = self.solveriterations + longitud = self.ropelenght2 + # hago un + 2 para que los segmentos sean los que hay entre los dos extremos... + 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" + # suelo: + 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" + bpy.ops.rigidbody.objects_add(type='PASSIVE') + # creamos el primer cubo: + cuboslink = [] + n = 0 + for i in range(segmentos): + # si es 0 le digo que empieza desde 1 + if i == 0: + i = offset_del_suelo + else: # si no es 0 les tengo que sumar uno para que no se pisen al empezar el primero desde 1 + i = i + offset_del_suelo + separacion = longitud * 2 / segmentos # distancia entre los cubos link + 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() + seleccionar_por_nombre + for i in range(segmentos - 1): + if i == 0: + seleccionar_por_nombre("Constraint") + else: + if i <= 9 and i > 0: + seleccionar_por_nombre("Constraint.00" + str(i)) + else: + if i <= 99 and i > 9: + seleccionar_por_nombre("Constraint.0" + str(i)) + else: + if i <= 999 and i > 99: + seleccionar_por_nombre("Constraint." + str(i)) + for c in bpy.context.selected_objects: + c.rigid_body_constraint.type = 'POINT' + deseleccionar_todo() + + # creamos la curva bezier: + 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" + for i in range(len(cuboslink)): + cubonombre = cuboslink[i].name + seleccionar_por_nombre(cubonombre) + seleccionar_por_nombre("Cuerda") + x = cuboslink[i].location[0] + y = cuboslink[i].location[1] + z = cuboslink[i].location[2] + # si es 0 le digo que empieza desde 1 es el offset desde el suelo... + if i == 0: + i = offset_del_suelo + else: # si no es 0 les tengo que sumar uno para que no se pisen al empezar el primero desde 1 + i = i + offset_del_suelo + salir_de_editmode() + # entrar_en_editmode() + tab_editmode() + if i == 1: + # selecciono todos los vertices y los borro + select_all_vertex_in_curve_bezier(bpy.data.objects["Cuerda"]) + bpy.ops.curve.delete(type='VERT') + # creamos el primer vertice: + bpy.ops.curve.vertex_add(location=(x, y, z)) + else: + # extruimos el resto: + 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() + + # creando la esfera 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') + + # lo subo todo para arriba un poco mas: + seleccionar_todo() + deseleccionar_por_nombre("groundplane") + 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 + + # para mover todo desde el primero de arriba: + seleccionar_por_nombre(cuboslink[-1].name) + bpy.ops.view3d.snap_cursor_to_selected() + seleccionar_todo() + deseleccionar_por_nombre("groundplane") + 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("Cuerda") + 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=310) + + def draw(self, context): + layout = self.layout + box = layout.box() + col = box.column() + col.label("Rope settings:") + rowsub0 = col.row() + rowsub0.prop(self, "hidecubes", text='Hide Link Cubes') + rowsub1 = col.row() + rowsub1.prop(self, "ropelenght2", text='Length') + rowsub1.prop(self, "ropesegments2", text='Segments') + rowsub2 = col.row() + rowsub2.prop(self, "radiuscubes", text='Radius Link Cubes') + rowsub2.prop(self, "radiusrope", text='Radius Rope') + rowsub3 = col.row() + 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/scene_objects_bi.py b/add_advanced_objects/scene_objects_bi.py new file mode 100644 index 00000000..db3ec9cd --- /dev/null +++ b/add_advanced_objects/scene_objects_bi.py @@ -0,0 +1,185 @@ +# 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 = "BI Scene with Objects" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + 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 + + return {"FINISHED"} diff --git a/add_advanced_objects/scene_objects_cycles.py b/add_advanced_objects/scene_objects_cycles.py new file mode 100644 index 00000000..1e07b3e5 --- /dev/null +++ b/add_advanced_objects/scene_objects_cycles.py @@ -0,0 +1,130 @@ +# 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 Scene with Objects" + bl_options = {'REGISTER'} + + def execute(self, context): + blend_data = context.blend_data + # ob = bpy.context.active_object + + # 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 + + return {'FINISHED'} diff --git a/add_advanced_objects/scene_texture_render.py b/add_advanced_objects/scene_texture_render.py new file mode 100644 index 00000000..2025b979 --- /dev/null +++ b/add_advanced_objects/scene_texture_render.py @@ -0,0 +1,66 @@ +# 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 Scene: Camera aligned to plane" + bl_options = {'REGISTER', 'UNDO'} + + def execute(self, context): + blend_data = context.blend_data + # ob = bpy.context.active_object + + # 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') + return {'FINISHED'} diff --git a/add_advanced_objects/trilighting.py b/add_advanced_objects/trilighting.py new file mode 100644 index 00000000..d23b4dbc --- /dev/null +++ b/add_advanced_objects/trilighting.py @@ -0,0 +1,225 @@ +# 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 active object in the scene") + 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 type off Key Light you would like", + items=Light_Type_List, + default='HEMI' + ) + secondarytype = EnumProperty( + attr='tl_type', + name="Fill + Back Type", + description="Choose the type off secondary Light 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): + 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" + + return {'FINISHED'} diff --git a/add_advanced_objects/unfold_transition.py b/add_advanced_objects/unfold_transition.py new file mode 100644 index 00000000..a621ca54 --- /dev/null +++ b/add_advanced_objects/unfold_transition.py @@ -0,0 +1,337 @@ +bl_info = { + "name": "Unfold transition", + "version": (0, 1, 0), + "location": "Tool bar > Animation tab > UnFold Transition", + "description": "Simple unfold transition / animation, will separate faces and set up an armature", + "category": "Animation"} + +import bpy +from bpy.props import ( + BoolProperty, + EnumProperty, + FloatProperty, + IntProperty, + # PointerProperty, + ) +from bpy.types import ( + Operator, + Panel, + ) +from random import ( + randint, + uniform, + ) +from mathutils import Vector +from mathutils.geometry import intersect_point_line + + +bpy.types.WindowManager.modo = EnumProperty( + name="", + items=[("cursor", "3D Cursor", "Use the Distance to 3D Cursor"), + ("weight", "Weight Map", "Use a Painted Weight map"), + ("index", "Mesh Indices", "Use Faces and Vertices index")], + description="How to Sort Bones for animation", default="cursor" + ) +bpy.types.WindowManager.flip = BoolProperty( + name="Flipping Faces", + default=False, + description="Rotate faces around the Center & skip Scaling - " + "keep checked for both operators" + ) +bpy.types.WindowManager.fold_duration = IntProperty( + name="Total Time", + min=5, soft_min=25, + max=10000, soft_max=2500, + default=200, + description="Total animation length" + ) +bpy.types.WindowManager.sca_time = IntProperty( + name="Scale Time", + min=1, + max=5000, soft_max=500, + default=10, + description="Faces scaling time" + ) +bpy.types.WindowManager.rot_time = IntProperty( + name="Rotation Time", + min=1, soft_min=5, + max=5000, soft_max=500, + default=15, + description="Faces rotation time" + ) +bpy.types.WindowManager.rot_max = IntProperty( + name="Angle", + min=-180, + max=180, + default=135, + description="Faces rotation angle" + ) +bpy.types.WindowManager.fold_noise = IntProperty( + name="Noise", + min=0, + max=500, soft_max=50, + default=0, + description="Offset some faces animation" + ) +bpy.types.WindowManager.bounce = FloatProperty( + name="Bounce", + min=0, + max=10, soft_max=2.5, + default=0, + description="Add some bounce to rotation" + ) +bpy.types.WindowManager.from_point = BoolProperty( + name="Point", + default=False, + description="Scale faces from a Point instead of from an Edge" + ) +bpy.types.WindowManager.wiggle_rot = BoolProperty( + name="Wiggle", + default=False, + description="Use all Axis + Random Rotation instead of X Aligned" + ) + + +class Set_Up_Fold(Operator): + bl_idname = "object.set_up_fold" + bl_label = "Set Up Unfold" + bl_description = "Set up Faces and Bones for animation" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + return (bpy.context.object.type == "MESH") + + def execute(self, context): + bpy.ops.object.mode_set() + wm = context.window_manager + scn = bpy.context.scene + obj = bpy.context.object + dat = obj.data + fac = dat.polygons + ver = dat.vertices + + # try to cleanup traces of previous actions + bpy.ops.object.mode_set(mode="EDIT") + bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=True) + bpy.ops.object.mode_set() + old_vg = [vg for vg in obj.vertex_groups if vg.name.startswith("bone.")] + for vg in old_vg: + obj.vertex_groups.remove(vg) + if "UnFold" in obj.modifiers: + arm = obj.modifiers["UnFold"].object + rig = arm.data + try: + scn.objects.unlink(arm) + bpy.data.objects.remove(arm) + bpy.data.armatures.remove(rig) + except: + pass + obj.modifiers.remove(obj.modifiers["UnFold"]) + + # try to obtain the face sequence from the vertex weights + if wm.modo == "weight": + if len(obj.vertex_groups): + i = obj.vertex_groups.active.index + W = [] + for f in fac: + v_data = [] + for v in f.vertices: + try: + w = ver[v].groups[i].weight + v_data.append((w, v)) + except: + v_data.append((0, v)) + v_data.sort(reverse=True) + v1 = ver[v_data[0][1]].co + v2 = ver[v_data[1][1]].co + cen = Vector(f.center) + its = intersect_point_line(cen, v2, v1) + head = v2.lerp(v1, its[1]) + peso = sum([x[0] for x in v_data]) + W.append((peso, f.index, cen, head)) + W.sort(reverse=True) + S = [x[1:] for x in W] + else: + self.report({"INFO"}, "First paint a Weight Map for this object") + return {"FINISHED"} + + # separate the faces and sort them + bpy.ops.object.mode_set(mode="EDIT") + bpy.ops.mesh.select_all(action="SELECT") + bpy.ops.mesh.edge_split() + bpy.ops.mesh.select_all(action="SELECT") + if wm.modo == "cursor": + bpy.context.tool_settings.mesh_select_mode = [True, True, True] + bpy.ops.mesh.sort_elements(type="CURSOR_DISTANCE", elements={"VERT", "EDGE", "FACE"}) + bpy.context.tool_settings.mesh_select_mode = [False, False, True] + bpy.ops.object.mode_set() + + # Get sequence of faces and edges from the face / vertex indices + if wm.modo != "weight": + S = [] + for f in fac: + E = list(f.edge_keys) + E.sort() + v1 = ver[E[0][0]].co + v2 = ver[E[0][1]].co + cen = Vector(f.center) + its = intersect_point_line(cen, v2, v1) + head = v2.lerp(v1, its[1]) + S.append((f.index, f.center, head)) + + # create the armature and the modifier + arm = bpy.data.armatures.new("arm") + rig = bpy.data.objects.new("rig_" + obj.name, arm) + rig.matrix_world = obj.matrix_world + scn.objects.link(rig) + scn.objects.active = rig + bpy.ops.object.mode_set(mode="EDIT") + arm.draw_type = "WIRE" + rig.show_x_ray = True + mod = obj.modifiers.new("UnFold", "ARMATURE") + mod.show_in_editmode = True + mod.object = rig + + # create bones and vertex groups + root = arm.edit_bones.new("bone.000") + root.tail = (0, 0, 0) + root.head = (0, 0, 1) + root.select = True + vis = [False, True] + [False] * 30 + + for fb in S: + f = fac[fb[0]] + b = arm.edit_bones.new("bone.000") + if wm.flip: + b.tail, b.head = fb[2], fb[1] + else: + b.tail, b.head = fb[1], fb[2] + b.align_roll(f.normal) + b.select = False + b.layers = vis + b.parent = root + vg = obj.vertex_groups.new(b.name) + vg.add(f.vertices, 1, "ADD") + + bpy.ops.object.mode_set() + if wm.modo == "weight": + obj.vertex_groups.active_index = 0 + scn.objects.active = rig + obj.select = False + + return {"FINISHED"} + + +class Animate_Fold(Operator): + bl_idname = "object.animate_fold" + bl_label = "Animate Unfold" + bl_description = "Animate bones to simulate unfold... Starts on current frame" + bl_options = {"REGISTER", "UNDO"} + + @classmethod + def poll(cls, context): + obj = bpy.context.object + return (obj.type == "ARMATURE" and obj.is_visible(bpy.context.scene)) + + def execute(self, context): + obj = bpy.context.object + scn = bpy.context.scene + fra = scn.frame_current + wm = context.window_manager + + # clear the animation and get the list of bones + if obj.animation_data: + obj.animation_data_clear() + bpy.ops.object.mode_set(mode="POSE") + bones = obj.pose.bones[0].children_recursive + if wm.flip: + rot = -3.141592 + else: + rot = wm.rot_max / 57.3 + extra = wm.rot_time * wm.bounce + ruido = max(wm.rot_time + extra, wm.sca_time) + wm.fold_noise + vel = (wm.fold_duration - ruido) / len(bones) + + # introduce scale and rotation keyframes + for a, b in enumerate(bones): + t = fra + a * vel + randint(0, wm.fold_noise) + if wm.flip: + b.scale = (1, 1, 1) + elif wm.from_point: + b.scale = (0, 0, 0) + else: + b.scale = (1, 0, 0) + if not wm.flip: + b.keyframe_insert("scale", frame=t) + b.scale = (1, 1, 1) + b.keyframe_insert("scale", frame=t + wm.sca_time) + if wm.rot_max: + b.rotation_mode = "XYZ" + if wm.wiggle_rot: + euler = (uniform(-rot, rot), uniform(-rot, rot), uniform(-rot, rot)) + else: + euler = (rot, 0, 0) + b.rotation_euler = euler + b.keyframe_insert("rotation_euler", frame=t) + if wm.bounce: + val = wm.bounce * -.10 + b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2]) + b.keyframe_insert("rotation_euler", frame=t + wm.rot_time + .25 * extra) + val = wm.bounce * .05 + b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2]) + b.keyframe_insert("rotation_euler", frame=t + wm.rot_time + .50 * extra) + val = wm.bounce * -.025 + b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2]) + b.keyframe_insert("rotation_euler", frame=t + wm.rot_time + .75 * extra) + b.rotation_euler = (0, 0, 0) + b.keyframe_insert("rotation_euler", frame=t + wm.rot_time + extra) + + return {"FINISHED"} + + +class PanelFOLD(Panel): + bl_label = "Unfold Transition" + bl_space_type = "VIEW_3D" + bl_region_type = "TOOLS" + bl_category = "Create" + bl_context = "objectmode" + bl_options = {"DEFAULT_CLOSED"} + + def draw(self, context): + wm = context.window_manager + layout = self.layout + column = layout.column() + column.operator("object.set_up_fold", text="1. Set Up Unfold") + column.prop(wm, "modo") + column.prop(wm, "flip") + layout.separator() + column = layout.column() + column.operator("object.animate_fold", text="2. Animate Unfold") + column.prop(wm, "fold_duration") + column.prop(wm, "sca_time") + column.prop(wm, "rot_time") + column.prop(wm, "rot_max") + row = column.row(align=True) + row.prop(wm, "fold_noise") + row.prop(wm, "bounce") + row = column.row(align=True) + row.prop(wm, "wiggle_rot") + if not wm.flip: + row.prop(wm, "from_point") + + +def register(): + bpy.utils.register_class(Set_Up_Fold) + bpy.utils.register_class(Animate_Fold) + bpy.utils.register_class(PanelFOLD) + + +def unregister(): + bpy.utils.unregister_class(Set_Up_Fold) + bpy.utils.unregister_class(Animate_Fold) + bpy.utils.unregister_class(PanelFOLD) + + +if __name__ == "__main__": + register() |