initial commit, advanced objects: T51110
authormeta-androcto <meta.androcto1@gmail.com>
Fri, 7 Apr 2017 09:05:59 +0000 (19:05 +1000)
committermeta-androcto <meta.androcto1@gmail.com>
Fri, 7 Apr 2017 09:05:59 +0000 (19:05 +1000)
26 files changed:
add_advanced_objects/__init__.py [new file with mode: 0644]
add_advanced_objects/add_light_template.py [new file with mode: 0644]
add_advanced_objects/add_mesh_aggregate.py [new file with mode: 0644]
add_advanced_objects/arrange_on_curve.py [new file with mode: 0644]
add_advanced_objects/circle_array.py [new file with mode: 0644]
add_advanced_objects/copy2.py [new file with mode: 0644]
add_advanced_objects/cubester.py [new file with mode: 0644]
add_advanced_objects/delaunay_voronoi/DelaunayVoronoi.py [new file with mode: 0644]
add_advanced_objects/delaunay_voronoi/__init__.py [new file with mode: 0644]
add_advanced_objects/delaunay_voronoi/delaunayVoronoiBlender.py [new file with mode: 0644]
add_advanced_objects/delaunay_voronoi/oscurart_constellation.py [new file with mode: 0644]
add_advanced_objects/drop_to_ground.py [new file with mode: 0644]
add_advanced_objects/make_struts.py [new file with mode: 0644]
add_advanced_objects/mesh_easylattice.py [new file with mode: 0644]
add_advanced_objects/object_add_chain.py [new file with mode: 0644]
add_advanced_objects/object_laplace_lightning.py [new file with mode: 0644]
add_advanced_objects/object_mangle_tools.py [new file with mode: 0644]
add_advanced_objects/oscurart_chain_maker.py [new file with mode: 0644]
add_advanced_objects/pixelate_3d.py [new file with mode: 0644]
add_advanced_objects/random_box_structure.py [new file with mode: 0644]
add_advanced_objects/rope_alpha.py [new file with mode: 0644]
add_advanced_objects/scene_objects_bi.py [new file with mode: 0644]
add_advanced_objects/scene_objects_cycles.py [new file with mode: 0644]
add_advanced_objects/scene_texture_render.py [new file with mode: 0644]
add_advanced_objects/trilighting.py [new file with mode: 0644]
add_advanced_objects/unfold_transition.py [new file with mode: 0644]

diff --git a/add_advanced_objects/__init__.py b/add_advanced_objects/__init__.py
new file mode 100644 (file)
index 0000000..3c15d1b
--- /dev/null
@@ -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 (file)
index 0000000..312b2d9
--- /dev/null
@@ -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 (file)
index 0000000..c8ec0ac
--- /dev/null
@@ -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 (file)
index 0000000..9894de1
--- /dev/null
@@ -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 (file)
index 0000000..170ea7a
--- /dev/null
@@ -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 (file)
index 0000000..3afdef2
--- /dev/null
@@ -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 (file)
index 0000000..83053ad
--- /dev/null
@@ -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 (file)
index 0000000..18d7f38
--- /dev/null
@@ -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 (file)
index 0000000..1d210a2
--- /dev/null
@@ -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 (file)
index 0000000..e937e7a
--- /dev/null
@@ -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 (file)
index 0000000..babbfdc
--- /dev/null
@@ -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 (file)
index 0000000..801b7e9
--- /dev/null
@@ -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 (file)
index 0000000..7f754ea
--- /dev/null
@@ -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 (file)
index 0000000..13512ad
--- /dev/null
@@ -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 (file)
index 0000000..57babfd
--- /dev/null
@@ -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 (file)
index 0000000..ceaf6cd
--- /dev/null
@@ -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 (file)
index 0000000..81110ad
--- /dev/null
@@ -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 (file)
index 0000000..c336e44
--- /dev/null
@@ -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 (file)
index 0000000..90e129d
--- /dev/null
@@ -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 (file)
index 0000000..5440b8b
--- /dev/null
@@ -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 (file)
index 0000000..f040614
--- /dev/null
@@ -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 (file)
index 0000000..db3ec9c
--- /dev/null
@@ -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 =&n