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

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormeta-androcto <meta.androcto1@gmail.com>2017-04-15 06:49:21 +0300
committermeta-androcto <meta.androcto1@gmail.com>2017-04-15 06:49:21 +0300
commitbd42467c77f46801318883fb63b75de2f5ae6fac (patch)
tree733760808a5021642cce6dbbd6bea6ed3bbf4f35
parent5f883054ce5904d4d1fb34216530b6df9cb04a81 (diff)
Initial commit add advanced objects: T51110 T50357
-rw-r--r--add_advanced_objects/__init__.py252
-rw-r--r--add_advanced_objects/add_light_template.py136
-rw-r--r--add_advanced_objects/add_mesh_aggregate.py318
-rw-r--r--add_advanced_objects/arrange_on_curve.py362
-rw-r--r--add_advanced_objects/circle_array.py143
-rw-r--r--add_advanced_objects/copy2.py288
-rw-r--r--add_advanced_objects/cubester.py1133
-rw-r--r--add_advanced_objects/delaunay_voronoi/DelaunayVoronoi.py1000
-rw-r--r--add_advanced_objects/delaunay_voronoi/__init__.py52
-rw-r--r--add_advanced_objects/delaunay_voronoi/delaunayVoronoiBlender.py234
-rw-r--r--add_advanced_objects/delaunay_voronoi/oscurart_constellation.py106
-rw-r--r--add_advanced_objects/drop_to_ground.py315
-rw-r--r--add_advanced_objects/make_struts.py571
-rw-r--r--add_advanced_objects/mesh_easylattice.py381
-rw-r--r--add_advanced_objects/object_add_chain.py170
-rw-r--r--add_advanced_objects/object_laplace_lightning.py1370
-rw-r--r--add_advanced_objects/object_mangle_tools.py211
-rw-r--r--add_advanced_objects/oscurart_chain_maker.py280
-rw-r--r--add_advanced_objects/pixelate_3d.py104
-rw-r--r--add_advanced_objects/random_box_structure.py193
-rw-r--r--add_advanced_objects/rope_alpha.py762
-rw-r--r--add_advanced_objects/scene_objects_bi.py185
-rw-r--r--add_advanced_objects/scene_objects_cycles.py130
-rw-r--r--add_advanced_objects/scene_texture_render.py66
-rw-r--r--add_advanced_objects/trilighting.py225
-rw-r--r--add_advanced_objects/unfold_transition.py337
26 files changed, 9324 insertions, 0 deletions
diff --git a/add_advanced_objects/__init__.py b/add_advanced_objects/__init__.py
new file mode 100644
index 00000000..3c15d1b4
--- /dev/null
+++ b/add_advanced_objects/__init__.py
@@ -0,0 +1,252 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Contributed to by:
+# meta-androcto, Bill Currie, Jorge Hernandez - Melenedez Jacob Morris, Oscurart #
+# Rebellion, Antonis Karvelas, Eleanor Howick, lijenstina, Daniel Schalla, Domlysz #
+# Unnikrishnan(kodemax), Florian Meyer, Omar ahmed, Brian Hinton (Nichod), liero #
+# Dannyboy, Mano-Wii, Kursad Karatas, teldredge
+
+bl_info = {
+ "name": "Add Advanced Objects",
+ "author": "Meta Androcto,",
+ "version": (0, 1, 1),
+ "blender": (2, 78, 0),
+ "location": "View3D > Add ",
+ "description": "Add Object & Camera extras",
+ "warning": "",
+ "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6"
+ "/Py/Scripts",
+ "tracker_url": "",
+ "category": "Object"}
+
+if "bpy" in locals():
+ import importlib
+
+ importlib.reload(add_light_template)
+ importlib.reload(scene_objects_bi)
+ importlib.reload(scene_objects_cycles)
+ importlib.reload(scene_texture_render)
+ importlib.reload(trilighting)
+ importlib.reload(pixelate_3d)
+ importlib.reload(object_add_chain)
+ importlib.reload(drop_to_ground)
+ importlib.reload(circle_array)
+ importlib.reload(unfold_transition)
+ importlib.reload(copy2)
+ importlib.reload(make_struts)
+ importlib.reload(random_box_structure)
+ importlib.reload(cubester)
+ importlib.reload(rope_alpha)
+ importlib.reload(add_mesh_aggregate)
+ importlib.reload(object_mangle_tools)
+ importlib.reload(arrange_on_curve)
+ importlib.reload(object_laplace_lightning)
+ importlib.reload(mesh_easylattice)
+ importlib.reload(DelaunayVoronoi)
+ importlib.reload(delaunayVoronoiBlender)
+ importlib.reload(oscurart_constellation)
+ importlib.reload(oscurart_chain_maker)
+
+else:
+ from . import (
+ add_light_template,
+ scene_objects_bi,
+ scene_objects_cycles,
+ scene_texture_render,
+ trilighting,
+ pixelate_3d,
+ object_add_chain,
+ oscurart_chain_maker,
+ drop_to_ground,
+ circle_array,
+ unfold_transition,
+ copy2,
+ make_struts,
+ random_box_structure,
+ cubester,
+ rope_alpha,
+ add_mesh_aggregate,
+ object_mangle_tools,
+ arrange_on_curve,
+ object_laplace_lightning,
+ mesh_easylattice
+ )
+ from .delaunay_voronoi import (
+ DelaunayVoronoi,
+ delaunayVoronoiBlender,
+ oscurart_constellation
+ )
+
+import bpy
+from bpy.types import (
+ Menu,
+ AddonPreferences,
+ )
+
+
+class INFO_MT_scene_elements_add(Menu):
+ # Define the "scenes" menu
+ bl_idname = "INFO_MT_scene_elements"
+ bl_label = "Test scenes"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("bi.add_scene",
+ text="Scene_Objects_BI")
+ layout.operator("objects_cycles.add_scene",
+ text="Scene_Objects_Cycles")
+ layout.operator("objects_texture.add_scene",
+ text="Scene_Textures_Cycles")
+
+
+class INFO_MT_mesh_lamps_add(Menu):
+ # Define the "lights" menu
+ bl_idname = "INFO_MT_scene_lamps"
+ bl_label = "Lighting Sets"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("object.add_light_template",
+ text="Add Light Template")
+ layout.operator("object.trilighting",
+ text="Add Tri Lighting")
+
+
+class INFO_MT_mesh_chain_add(Menu):
+ # Define the "Chains" menu
+ bl_idname = "INFO_MT_mesh_chain"
+ bl_label = "Chains"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("mesh.primitive_chain_add", icon="LINKED")
+ layout.operator("mesh.primitive_oscurart_chain_add", icon="LINKED")
+
+
+class INFO_MT_array_mods_add(Menu):
+ # Define the "array" menu
+ bl_idname = "INFO_MT_array_mods"
+ bl_label = "Array Mods"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ self.layout.menu("INFO_MT_mesh_chain", icon="LINKED")
+ layout.operator("objects.circle_array_operator",
+ text="Circle Array", icon='MOD_ARRAY')
+ layout.operator("object.agregate_mesh",
+ text="Aggregate Mesh", icon='MOD_ARRAY')
+ obj = context.object
+ if obj.type in ['MESH',]:
+ layout.operator("mesh.copy2",
+ text="Copy To Vert/Edge", icon='MOD_ARRAY')
+
+
+
+class INFO_MT_quick_blocks_add(Menu):
+ # Define the "Blocks" menu
+ bl_idname = "INFO_MT_quick_tools"
+ bl_label = "Block Tools"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator('object.pixelate', icon='MESH_GRID')
+ obj = context.object
+ if obj.type in ['MESH',]:
+ layout.operator("mesh.generate_struts",
+ text="Struts", icon='GRID')
+ layout.operator("object.easy_lattice",
+ text="Easy Lattice", icon='MOD_LATTICE')
+ layout.operator("object.make_structure",
+ text="Random Boxes", icon='SEQ_SEQUENCER')
+
+
+class INFO_MT_Physics_tools_add(Menu):
+ # Define the "mesh objects" menu
+ bl_idname = "INFO_MT_physics_tools"
+ bl_label = "Physics Tools"
+
+ def draw(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ layout.operator("object.drop_on_active",
+ text="Drop To Ground")
+ layout.operator("ball.rope",
+ text="Wrecking Ball", icon='PHYSICS')
+ layout.operator("clot.rope",
+ text="Cloth Rope", icon='PHYSICS')
+
+
+# Define "Extras" menu
+def menu(self, context):
+ layout = self.layout
+ layout.operator_context = 'INVOKE_REGION_WIN'
+ self.layout.separator()
+ self.layout.menu("INFO_MT_scene_elements", icon="SCENE_DATA")
+ self.layout.menu("INFO_MT_scene_lamps", icon="LAMP_SPOT")
+ self.layout.separator()
+ self.layout.menu("INFO_MT_array_mods", icon="MOD_ARRAY")
+ self.layout.menu("INFO_MT_quick_tools", icon="MOD_BUILD")
+ self.layout.menu("INFO_MT_physics_tools", icon="PHYSICS")
+
+
+# Addons Preferences
+class AddonPreferences(AddonPreferences):
+ bl_idname = __name__
+
+ def draw(self, context):
+ layout = self.layout
+ layout.label(text="----Add Menu Advanced----")
+ layout.label(text="Quick Tools:")
+ layout.label(text="Drop, Pixelate & Wrecking Ball")
+ layout.label(text="Array Mods:")
+ layout.label(text="Circle Array, Chains, Vert to Edge, Aggregate")
+
+
+def register():
+ object_mangle_tools.register()
+ arrange_on_curve.register()
+ bpy.utils.register_module(__name__)
+ # Add "Extras" menu to the "Add" menu
+ bpy.types.INFO_MT_add.append(menu)
+ try:
+ bpy.types.VIEW3D_MT_AddMenu.append(menu)
+ except:
+ pass
+
+
+def unregister():
+ object_mangle_tools.unregister()
+ arrange_on_curve.unregister()
+ # Remove "Extras" menu from the "Add" menu.
+ bpy.types.INFO_MT_add.remove(menu)
+ try:
+ bpy.types.VIEW3D_MT_AddMenu.remove(menu)
+ except:
+ pass
+
+ bpy.utils.unregister_module(__name__)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/add_light_template.py b/add_advanced_objects/add_light_template.py
new file mode 100644
index 00000000..312b2d9e
--- /dev/null
+++ b/add_advanced_objects/add_light_template.py
@@ -0,0 +1,136 @@
+# gpl: author Rebellion
+
+import bpy
+from bpy.types import Operator
+from bpy.props import BoolProperty
+
+
+def add_lamps(self, context):
+ try:
+ if self.bKeyLight:
+ keyLight = bpy.data.lamps.new(name="Key_Light", type="SPOT")
+ ob = bpy.data.objects.new("Key_Light", keyLight)
+ constraint = ob.constraints.new(type='COPY_LOCATION')
+ constraint.use_offset = True
+ constraint.owner_space = 'LOCAL'
+ constraint.target = self.camera
+ constraint = ob.constraints.new(type='TRACK_TO')
+ constraint.target = self.target
+ constraint.track_axis = 'TRACK_NEGATIVE_Z'
+ constraint.up_axis = 'UP_X'
+ constraint.owner_space = 'LOCAL'
+ bpy.context.scene.objects.link(ob)
+ ob.rotation_euler[2] = -0.785398
+
+ if self.bFillLight:
+ fillLight = bpy.data.lamps.new(name="Fill_Light", type="SPOT")
+ ob = bpy.data.objects.new("Fill_Light", fillLight)
+ constraint = ob.constraints.new(type='COPY_LOCATION')
+ constraint.use_offset = True
+ constraint.owner_space = 'LOCAL'
+ constraint.target = self.camera
+ constraint = ob.constraints.new(type='TRACK_TO')
+ constraint.target = self.target
+ constraint.track_axis = 'TRACK_NEGATIVE_Z'
+ constraint.up_axis = 'UP_X'
+ constraint.owner_space = 'LOCAL'
+ bpy.context.scene.objects.link(ob)
+ ob.rotation_euler[2] = 0.785398
+ ob.data.energy = 0.3
+
+ if self.bBackLight:
+ backLight = bpy.data.lamps.new(name="Back_Light", type="SPOT")
+ ob = bpy.data.objects.new("Back_Light", backLight)
+ constraint = ob.constraints.new(type='COPY_LOCATION')
+ constraint.use_offset = True
+ constraint.owner_space = 'LOCAL'
+ constraint.target = self.camera
+ constraint = ob.constraints.new(type='TRACK_TO')
+ constraint.target = self.target
+ constraint.track_axis = 'TRACK_NEGATIVE_Z'
+ constraint.up_axis = 'UP_X'
+ constraint.owner_space = 'LOCAL'
+ bpy.context.scene.objects.link(ob)
+ ob.rotation_euler[2] = 3.14159
+ ob.data.energy = 0.2
+
+ if self.camera_constraint:
+ constraint = self.camera.constraints.new(type='TRACK_TO')
+ constraint.target = self.target
+ constraint.track_axis = 'TRACK_NEGATIVE_Z'
+ constraint.up_axis = 'UP_Y'
+
+ except Exception as e:
+ self.report({'WARNING'},
+ "Some operations could not be performed (See Console for more info)")
+
+ print("\n[object.add_light_template]\nError: {}".format(e))
+
+
+class OBJECT_OT_add_light_template(Operator):
+ bl_idname = "object.add_light_template"
+ bl_label = "Add Light Template"
+ bl_description = "Add Key, Fill & Back Lights"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ camera = None
+ target = None
+
+ bKeyLight = BoolProperty(
+ name="Key Light",
+ default=True
+ )
+ bFillLight = BoolProperty(
+ name="Fill Light",
+ default=True
+ )
+ bBackLight = BoolProperty(
+ name="Back Light",
+ default=True
+ )
+ camera_constraint = BoolProperty(
+ name="Camera Constraint",
+ default=False
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def execute(self, context):
+ objects = context.selected_objects
+
+ if len(objects) == 2:
+ for ob in objects:
+ if ob.type == 'CAMERA':
+ self.camera = ob
+ else:
+ self.target = ob
+ elif len(objects) == 1:
+ if objects[0].type == 'CAMERA':
+ self.camera = objects[0]
+ bpy.ops.object.empty_add()
+ self.target = context.active_object
+ else:
+ self.camera = context.scene.camera
+ self.target = context.active_object
+ elif len(objects) == 0:
+ bpy.ops.object.empty_add()
+ self.target = context.active_object
+ self.camera = context.scene.camera
+
+ add_lamps(self, context)
+
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_class(OBJECT_OT_add_light_template)
+
+
+def unregister():
+ bpy.utils.unregister_class(OBJECT_OT_add_light_template)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/add_mesh_aggregate.py b/add_advanced_objects/add_mesh_aggregate.py
new file mode 100644
index 00000000..c8ec0ac5
--- /dev/null
+++ b/add_advanced_objects/add_mesh_aggregate.py
@@ -0,0 +1,318 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Simple aggregate of particles / meshes
+# Copy the selected objects on the active object
+# Based on the position of the cursor and a defined volume
+# Allows to control growth by using a Build modifier
+
+bl_info = {
+ "name": "Aggregate Mesh",
+ "author": "liero",
+ "version": (0, 0, 5),
+ "blender": (2, 7, 0),
+ "location": "View3D > Tool Shelf",
+ "description": "Adds geometry to a mesh like in DLA aggregators",
+ "category": "Object"}
+
+
+import bpy
+import bmesh
+from random import (
+ choice,
+ gauss,
+ seed,
+ )
+from mathutils import Matrix
+from bpy.props import (
+ BoolProperty,
+ FloatProperty,
+ IntProperty,
+ )
+from bpy.types import Operator
+
+
+def use_random_seed(self):
+ seed(self.rSeed)
+ return
+
+
+def rg(n):
+ return (round(gauss(0, n), 2))
+
+
+def remover(sel=False):
+ bpy.ops.object.editmode_toggle()
+ if sel:
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.remove_doubles(threshold=0.0001)
+ bpy.ops.object.mode_set()
+
+
+class OBJECT_OT_agregate_mesh(Operator):
+ bl_idname = "object.agregate_mesh"
+ bl_label = "Aggregate"
+ bl_description = ("Adds geometry to a mesh like in DLA aggregators\n"
+ "Needs at least two selected Mesh objects")
+ bl_options = {'REGISTER', 'UNDO', 'PRESET'}
+
+ updateMeNow = BoolProperty(
+ name="Update",
+ description="Update",
+ default=True
+ )
+ volX = FloatProperty(
+ name="Volume X",
+ min=0.1, max=25,
+ default=3,
+ description="The cloud around cursor"
+ )
+ volY = FloatProperty(
+ name="Volume Y",
+ min=0.1, max=25,
+ default=3,
+ description="The cloud around cursor"
+ )
+ volZ = FloatProperty(
+ name="Volume Z",
+ min=0.1, max=25,
+ default=3,
+ description="The cloud around cursor"
+ )
+ baseSca = FloatProperty(
+ name="Scale",
+ min=0.01, max=5,
+ default=.25,
+ description="Particle Scale"
+ )
+ varSca = FloatProperty(
+ name="Var",
+ min=0, max=1,
+ default=0,
+ description="Particle Scale Variation"
+ )
+ rotX = FloatProperty(
+ name="Rot Var X",
+ min=0, max=2,
+ default=0,
+ description="X Rotation Variation"
+ )
+ rotY = FloatProperty(
+ name="Rot Var Y",
+ min=0, max=2,
+ default=0,
+ description="Y Rotation Variation"
+ )
+ rotZ = FloatProperty(
+ name="Rot Var Z",
+ min=0, max=2,
+ default=1,
+ description="Z Rotation Variation"
+ )
+ rSeed = IntProperty(
+ name="Random seed",
+ min=0, max=999999,
+ default=1,
+ description="Seed to feed random values"
+ )
+ numP = IntProperty(
+ name="Number",
+ min=1,
+ max=9999, soft_max=500,
+ default=50,
+ description="Number of particles"
+ )
+ nor = BoolProperty(
+ name="Normal Oriented",
+ default=False,
+ description="Align Z axis with Faces normals"
+ )
+ cent = BoolProperty(
+ name="Use Face Center",
+ default=False,
+ description="Center on Faces"
+ )
+ track = BoolProperty(
+ name="Cursor Follows",
+ default=False,
+ description="Cursor moves as structure grows / more compact results"
+ )
+ anim = BoolProperty(
+ name="Animatable",
+ default=False,
+ description="Sort faces so you can regrow with Build Modifier, materials are lost"
+ )
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column(align=True)
+ col.prop(self, "updateMeNow", toggle=True)
+ col.separator()
+
+ col = layout.column(align=True)
+ col.prop(self, "volX", slider=True)
+ col.prop(self, "volY", slider=True)
+ col.prop(self, "volZ", slider=True)
+
+ layout.label(text="Particles:")
+ col = layout.column(align=True)
+ col.prop(self, "baseSca", slider=True)
+ col.prop(self, "varSca", slider=True)
+
+ col = layout.column(align=True)
+ col.prop(self, "rotX", slider=True)
+ col.prop(self, "rotY", slider=True)
+ col.prop(self, "rotZ", slider=True)
+
+ col = layout.column(align=True)
+ col.prop(self, "rSeed", slider=False)
+
+ col = layout.column(align=True)
+ col.prop(self, "nor")
+ col.prop(self, "cent")
+ col.prop(self, "track")
+ col.prop(self, "anim")
+
+ col.prop(self, 'numP')
+
+ @classmethod
+ def poll(cls, context):
+ return(len(bpy.context.selected_objects) > 1 and bpy.context.object.type == 'MESH')
+
+ def invoke(self, context, event):
+ self.updateMeNow = True
+ return self.execute(context)
+
+ def execute(self, context):
+ if not self.updateMeNow:
+ return {'PASS_THROUGH'}
+
+ scn = bpy.context.scene
+ obj = bpy.context.active_object
+
+ use_random_seed(self)
+
+ mat = Matrix((
+ (1, 0, 0, 0),
+ (0, 1, 0, 0),
+ (0, 0, 1, 0),
+ (0, 0, 0, 1))
+ )
+ if obj.matrix_world != mat:
+ self.report({'WARNING'}, "Apply transformations to Active Object first!")
+ return{'FINISHED'}
+ par = [o for o in bpy.context.selected_objects if o.type == 'MESH' and o != obj]
+ if not par:
+ return{'FINISHED'}
+
+ bpy.ops.object.mode_set()
+ bpy.ops.object.select_all(action='DESELECT')
+ obj.select = True
+ msv = []
+
+ for i in range(len(obj.modifiers)):
+ msv.append(obj.modifiers[i].show_viewport)
+ obj.modifiers[i].show_viewport = False
+
+ cur = scn.cursor_location
+ for i in range(self.numP):
+
+ mes = choice(par).data
+ newobj = bpy.data.objects.new('nuevo', mes)
+ scn.objects.link(newobj)
+ origen = (rg(self.volX) + cur[0], rg(self.volY) + cur[1], rg(self.volZ) + cur[2])
+
+ cpom = obj.closest_point_on_mesh(origen)
+
+ if self.cent:
+ bm = bmesh.new()
+ bm.from_mesh(obj.data)
+ if hasattr(bm.verts, "ensure_lookup_table"):
+ bm.verts.ensure_lookup_table()
+ bm.faces.ensure_lookup_table()
+
+ newobj.location = bm.faces[cpom[3]].calc_center_median()
+
+ bm.free()
+ else:
+ newobj.location = cpom[1]
+
+ if self.nor:
+ newobj.rotation_mode = 'QUATERNION'
+ newobj.rotation_quaternion = cpom[1].to_track_quat('Z', 'Y')
+ newobj.rotation_mode = 'XYZ'
+ newobj.rotation_euler[0] += rg(self.rotX)
+ newobj.rotation_euler[1] += rg(self.rotY)
+ newobj.rotation_euler[2] += rg(self.rotZ)
+ else:
+ newobj.rotation_euler = (rg(self.rotX), rg(self.rotY), rg(self.rotZ))
+
+ newobj.scale = [self.baseSca + self.baseSca * rg(self.varSca)] * 3
+
+ if self.anim:
+ newobj.select = True
+ bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', obdata=True)
+ bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
+
+ bme = bmesh.new()
+ bme.from_mesh(obj.data)
+
+ tmp = bmesh.new()
+ tmp.from_mesh(newobj.data)
+
+ for f in tmp.faces:
+ # z = len(bme.verts)
+ for v in f.verts:
+ bme.verts.new(list(v.co))
+ bme.faces.new(bme.verts[-len(f.verts):])
+
+ bme.to_mesh(obj.data)
+ remover(True)
+
+ newobj.data.user_clear()
+ bpy.data.meshes.remove(newobj.data)
+
+ else:
+ scn.objects.active = obj
+ newobj.select = True
+ bpy.ops.object.join()
+
+ if self.track:
+ cur = scn.cursor_location = cpom[1]
+
+ for i in range(len(msv)):
+ obj.modifiers[i].show_viewport = msv[i]
+
+ for o in par:
+ o.select = True
+
+ obj.select = True
+
+ return{'FINISHED'}
+
+
+def register():
+ bpy.utils.register_class(OBJECT_OT_agregate_mesh)
+
+
+def unregister():
+ bpy.utils.unregister_class(OBJECT_OT_agregate_mesh)
+
+
+if __name__ == '__main__':
+ register()
diff --git a/add_advanced_objects/arrange_on_curve.py b/add_advanced_objects/arrange_on_curve.py
new file mode 100644
index 00000000..9894de12
--- /dev/null
+++ b/add_advanced_objects/arrange_on_curve.py
@@ -0,0 +1,362 @@
+# gpl author: Mano-Wii
+
+bl_info = {
+ "name": "Arrange on Curve",
+ "author": "Mano-Wii",
+ "version": (6, 3, 0),
+ "blender": (2, 7, 7),
+ "location": "View3D > TOOLS",
+ "description": "Arrange objects along a curve",
+ "warning": "Select curve",
+ "wiki_url": "",
+ "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
+ "category": "3D View"
+ }
+
+import bpy
+import mathutils
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+from bpy.props import (
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ IntProperty,
+ StringProperty,
+ )
+
+FLT_MIN = 0.004
+
+
+class PanelDupliCurve(Panel):
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_context = "objectmode"
+ bl_category = "Create"
+ bl_label = "Duplicate on curve"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ @classmethod
+ def poll(cls, context):
+ return context.object and context.mode == 'OBJECT' and context.object.type == 'CURVE'
+
+ def draw(self, context):
+ layout = self.layout
+ layout.prop(context.scene, "use_selected")
+ if not context.scene.use_selected:
+ layout.prop(context.scene, "select_type", expand=True)
+ if context.scene.select_type == 'O':
+ layout.column(align=True).prop_search(
+ context.scene, "objeto_arranjar",
+ bpy.data, "objects"
+ )
+ elif context.scene.select_type == 'G':
+ layout.column(align=True).prop_search(
+ context.scene, "objeto_arranjar",
+ bpy.data, "groups"
+ )
+ if context.object.type == 'CURVE':
+ layout.operator("object.arranjar_numa_curva", text="Arrange Objects")
+
+
+class DupliCurve(Operator):
+ bl_idname = "object.arranjar_numa_curva"
+ bl_label = "Arrange Objects"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ use_distance = EnumProperty(
+ items=[
+ ("D", "Distance", "Objects are arranged depending on the distance", 0),
+ ("Q", "Quantity", "Objects are arranged depending on the quantity", 1),
+ ("R", "Range", "Objects are arranged uniformly between the corners", 2)
+ ]
+ )
+ distance = FloatProperty(
+ name="Distance",
+ description="Distancia entre objetos",
+ default=1.0,
+ min=FLT_MIN,
+ soft_min=0.1,
+ unit='LENGTH',
+ )
+ object_qt = IntProperty(
+ name="Quantity",
+ description="Object amount.",
+ default=2,
+ min=0,
+ )
+ scale = FloatProperty(
+ name="Scale",
+ description="Object Scale",
+ default=1.0,
+ min=FLT_MIN,
+ unit='LENGTH',
+ )
+ Yaw = FloatProperty(
+ default=0.0,
+ name="X",
+ unit='ROTATION'
+ )
+ Pitch = FloatProperty(
+ default=0.0,
+ name="Y",
+ unit='ROTATION'
+ )
+ Roll = FloatProperty(
+ default=0.0,
+ name="Z",
+ unit='ROTATION'
+ )
+ max_angle = FloatProperty(
+ default=1.57079,
+ max=3.141592,
+ name="Angle",
+ unit='ROTATION'
+ )
+ offset = FloatProperty(
+ default=0.0,
+ name="offset",
+ unit='LENGTH'
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.mode == 'OBJECT'
+
+ def draw(self, context):
+ layout = self.layout
+ col = layout.column()
+ col.prop(self, "use_distance", text="")
+ col = layout.column(align=True)
+ if self.use_distance == "D":
+ col.prop(self, "distance")
+ elif self.use_distance == "Q":
+ col.prop(self, "object_qt")
+ else:
+ col.prop(self, "distance")
+ col.prop(self, "max_angle")
+ col.prop(self, "offset")
+
+ col = layout.column(align=True)
+ col.prop(self, "scale")
+ col.prop(self, "Yaw")
+ col.prop(self, "Pitch")
+ col.prop(self, "Roll")
+
+ def Glpoints(self, curve):
+ Gpoints = []
+ for i, spline in enumerate(curve.data.splines):
+ segments = len(spline.bezier_points)
+ if segments >= 2:
+ r = spline.resolution_u + 1
+
+ points = []
+ for j in range(segments):
+ bp1 = spline.bezier_points[j]
+ inext = (j + 1)
+ if inext == segments:
+ if not spline.use_cyclic_u:
+ break
+ inext = 0
+ bp2 = spline.bezier_points[inext]
+ if bp1.handle_right_type == bp2.handle_left_type == 'VECTOR':
+ _points = (bp1.co, bp2.co) if j == 0 else (bp2.co,)
+ else:
+ knot1 = bp1.co
+ handle1 = bp1.handle_right
+ handle2 = bp2.handle_left
+ knot2 = bp2.co
+ _points = mathutils.geometry.interpolate_bezier(knot1, handle1, handle2, knot2, r)
+ points.extend(_points)
+ Gpoints.append(tuple((curve.matrix_world * p for p in points)))
+ elif len(spline.points) >= 2:
+ l = [curve.matrix_world * p.co.xyz for p in spline.points]
+ if spline.use_cyclic_u:
+ l.append(l[0])
+ Gpoints.append(tuple(l))
+
+ if self.use_distance == "R":
+ max_angle = self.max_angle
+ tmp_Gpoints = []
+ sp = Gpoints[i]
+ sp2 = [sp[0], sp[1]]
+ lp = sp[1]
+ v1 = lp - sp[0]
+ for p in sp[2:]:
+ v2 = p - lp
+ try:
+ if (3.14158 - v1.angle(v2)) < max_angle:
+ tmp_Gpoints.append(tuple(sp2))
+ sp2 = [lp]
+ except Exception as e:
+ print(e)
+ pass
+ sp2.append(p)
+ v1 = v2
+ lp = p
+ tmp_Gpoints.append(tuple(sp2))
+ Gpoints = Gpoints[:i] + tmp_Gpoints
+
+ lengths = []
+ if self.use_distance != "D":
+ for sp in Gpoints:
+ lp = sp[1]
+ leng = (lp - sp[0]).length
+ for p in sp[2:]:
+ leng += (p - lp).length
+ lp = p
+ lengths.append(leng)
+ return Gpoints, lengths
+
+ def execute(self, context):
+ if context.object.type != 'CURVE':
+ return {'CANCELLED'}
+
+ curve = context.active_object
+ Gpoints, lengs = self.Glpoints(curve)
+
+ if context.scene.use_selected:
+ G_Objeto = context.selected_objects
+ G_Objeto.remove(curve)
+ if not G_Objeto:
+ return {'CANCELLED'}
+ elif context.scene.select_type == 'O':
+ G_Objeto = bpy.data.objects[context.scene.objeto_arranjar],
+ elif context.scene.select_type == 'G':
+ G_Objeto = bpy.data.groups[context.scene.objeto_arranjar].objects
+ yawMatrix = mathutils.Matrix.Rotation(self.Yaw, 4, 'X')
+ pitchMatrix = mathutils.Matrix.Rotation(self.Pitch, 4, 'Y')
+ rollMatrix = mathutils.Matrix.Rotation(self.Roll, 4, 'Z')
+
+ max_angle = self.max_angle # is this used?
+
+ if self.use_distance == "D":
+ dist = self.distance
+ for sp_points in Gpoints:
+ dx = 0.0 # Length of initial calculation of section
+ last_point = sp_points[0]
+ j = 0
+ for point in sp_points[1:]:
+ vetorx = point - last_point # Vector spline section
+ quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z') # Tracking the selected objects
+ quat = quat.to_matrix().to_4x4()
+
+ v_len = vetorx.length
+ if v_len > 0.0:
+ dx += v_len # Defined length calculation equal total length of the spline section
+ v_norm = vetorx / v_len
+ while dx > dist:
+ object = G_Objeto[j % len(G_Objeto)]
+ j += 1
+ dx -= dist # Calculating the remaining length of the section
+ obj = object.copy()
+ context.scene.objects.link(obj)
+ obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix
+ # Placing in the correct position
+ obj.matrix_world.translation = point - v_norm * dx
+ obj.scale *= self.scale
+ last_point = point
+
+ elif self.use_distance == "Q":
+ object_qt = self.object_qt + 1
+ for i, sp_points in enumerate(Gpoints):
+ dx = 0.0 # Length of initial calculation of section
+ dist = lengs[i] / object_qt
+ last_point = sp_points[0]
+ j = 0
+ for point in sp_points[1:]:
+ vetorx = point - last_point # Vector spline section
+ # Tracking the selected objects
+ quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z')
+ quat = quat.to_matrix().to_4x4()
+
+ v_len = vetorx.length
+ if v_len > 0.0:
+ # Defined length calculation equal total length of the spline section
+ dx += v_len
+ v_norm = vetorx / v_len
+ while dx > dist:
+ object = G_Objeto[j % len(G_Objeto)]
+ j += 1
+ dx -= dist # Calculating the remaining length of the section
+ obj = object.copy()
+ context.scene.objects.link(obj)
+ obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix
+ # Placing in the correct position
+ obj.matrix_world.translation = point - v_norm * dx
+ obj.scale *= self.scale
+ last_point = point
+
+ else:
+ dist = self.distance
+ offset2 = 2 * self.offset
+ for i, sp_points in enumerate(Gpoints):
+ leng = lengs[i] - offset2
+ rest = leng % dist
+ offset = offset2 + rest
+ leng -= rest
+ offset /= 2
+ last_point = sp_points[0]
+
+ dx = dist - offset # Length of initial calculation of section
+ j = 0
+ for point in sp_points[1:]:
+ vetorx = point - last_point # Vector spline section
+ # Tracking the selected objects
+ quat = mathutils.Vector.to_track_quat(vetorx, 'X', 'Z')
+ quat = quat.to_matrix().to_4x4()
+
+ v_len = vetorx.length
+ if v_len > 0.0:
+ dx += v_len
+ v_norm = vetorx / v_len
+ while dx >= dist and leng >= 0.0:
+ leng -= dist
+ dx -= dist # Calculating the remaining length of the section
+ object = G_Objeto[j % len(G_Objeto)]
+ j += 1
+ obj = object.copy()
+ context.scene.objects.link(obj)
+ obj.matrix_world = quat * yawMatrix * pitchMatrix * rollMatrix
+ # Placing in the correct position
+ obj.matrix_world.translation = point - v_norm * dx
+ obj.scale *= self.scale
+ last_point = point
+
+ return {"FINISHED"}
+
+
+def register():
+ bpy.utils.register_class(PanelDupliCurve)
+ bpy.utils.register_class(DupliCurve)
+ bpy.types.Scene.use_selected = BoolProperty(
+ name='Use Selected',
+ description='Use the selected objects to duplicate',
+ default=True,
+ )
+ bpy.types.Scene.objeto_arranjar = StringProperty(
+ name=""
+ )
+ bpy.types.Scene.select_type = EnumProperty(
+ name="Type",
+ description="Select object or group",
+ items=[
+ ('O', "OBJECT", "make duplicates of a specific object"),
+ ('G', "GROUP", "make duplicates of the objects in a group"),
+ ],
+ default='O',
+ )
+
+
+def unregister():
+ bpy.utils.unregister_class(PanelDupliCurve)
+ bpy.utils.unregister_class(DupliCurve)
+ del bpy.types.Scene.objeto_arranjar
+ del bpy.types.Scene.use_selected
+ del bpy.types.Scene.select_type
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/circle_array.py b/add_advanced_objects/circle_array.py
new file mode 100644
index 00000000..170ea7a6
--- /dev/null
+++ b/add_advanced_objects/circle_array.py
@@ -0,0 +1,143 @@
+# gpl author: Antonis Karvelas
+
+# -*- coding: utf-8 -*-
+
+bl_info = {
+ "name": "Circle Array",
+ "author": "Antonis Karvelas",
+ "version": (1, 0),
+ "blender": (2, 6, 7),
+ "location": "View3D > Object > Circle_Array",
+ "description": "Uses an existing array and creates an empty, "
+ "rotates it properly and makes a Circle Array",
+ "warning": "",
+ "wiki_url": "",
+ "tracker_url": "",
+ "category": "Mesh"
+ }
+
+
+import bpy
+from bpy.types import Operator
+from math import radians
+
+
+class Circle_Array(Operator):
+ bl_label = "Circle Array"
+ bl_idname = "objects.circle_array_operator"
+ bl_description = ("Creates an Array Modifier with offset empty object\n"
+ "Works with Mesh, Curve, Text & Surface")
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def check_empty_name(self, context):
+ new_name, def_name = "", "EMPTY_C_Array"
+ suffix = 1
+ try:
+ # first slap a simple linear count + 1 for numeric suffix, if it fails
+ # harvest for the rightmost numbers and append the max value
+ list_obj = []
+ obj_all = context.scene.objects
+ list_obj = [obj.name for obj in obj_all if obj.name.startswith(def_name)]
+ new_name = "{}_{}".format(def_name, len(list_obj) + 1)
+
+ if new_name in list_obj:
+ from re import findall
+ test_num = [findall("\d+", words) for words in list_obj]
+ suffix += max([int(l[-1]) for l in test_num])
+ new_name = "{}_{}".format(def_name, suffix)
+ return new_name
+ except:
+ return None
+
+ def execute(self, context):
+ try:
+ allowed_obj = ['MESH', 'CURVE', 'SURFACE', 'FONT']
+ if context.active_object.type not in allowed_obj:
+ self.report(
+ {"WARNING"},
+ "Operation Cancelled. The active object is not of "
+ "Mesh, Curve, Surface or Font type"
+ )
+ return {'CANCELLED'}
+
+ default_name = self.check_empty_name(context) or "EMPTY_C_Array"
+ bpy.ops.object.modifier_add(type='ARRAY')
+
+ if len(bpy.context.selected_objects) == 2:
+ list = bpy.context.selected_objects
+ active = list[0]
+ active.modifiers[0].use_object_offset = True
+ active.modifiers[0].use_relative_offset = False
+ active.select = False
+ bpy.context.scene.objects.active = list[0]
+ bpy.ops.view3d.snap_cursor_to_selected()
+ if active.modifiers[0].offset_object is None:
+ bpy.ops.object.add(type='EMPTY')
+ empty_name = bpy.context.active_object
+ empty_name.name = default_name
+ active.modifiers[0].offset_object = empty_name
+ else:
+ empty_name = active.modifiers[0].offset_object
+
+ bpy.context.scene.objects.active = active
+ num = active.modifiers["Array"].count
+ rotate_num = 360 / num
+ active.select = True
+ bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
+ empty_name.rotation_euler = (0, 0, radians(rotate_num))
+ empty_name.select = False
+ active.select = True
+ bpy.ops.object.origin_set(type="ORIGIN_CURSOR")
+
+ return {'FINISHED'}
+ else:
+ active = context.active_object
+ active.modifiers[0].use_object_offset = True
+ active.modifiers[0].use_relative_offset = False
+ bpy.ops.view3d.snap_cursor_to_selected()
+ if active.modifiers[0].offset_object is None:
+ bpy.ops.object.add(type='EMPTY')
+ empty_name = bpy.context.active_object
+ empty_name.name = default_name
+ active.modifiers[0].offset_object = empty_name
+ else:
+ empty_name = active.modifiers[0].offset_object
+
+ bpy.context.scene.objects.active = active
+ num = active.modifiers["Array"].count
+ rotate_num = 360 / num
+ active.select = True
+ bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
+ empty_name.rotation_euler = (0, 0, radians(rotate_num))
+ empty_name.select = False
+ active.select = True
+
+ return {'FINISHED'}
+ except Exception as e:
+ self.report({'WARNING'},
+ "Circle Array operator could not be executed (See the console for more info)")
+ print("\n[objects.circle_array_operator]\nError: {}\n".format(e))
+
+ return {'CANCELLED'}
+
+
+# Register
+def circle_array_menu(self, context):
+ self.layout.operator(Circle_Array.bl_idname, text="Circle_Array")
+
+
+def register():
+ bpy.utils.register_class(Circle_Array)
+ bpy.types.VIEW3D_MT_object.append(circle_array_menu)
+
+
+def unregister():
+ bpy.utils.unregister_class(Circle_Array)
+ bpy.types.VIEW3D_MT_object.remove(circle_array_menu)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/copy2.py b/add_advanced_objects/copy2.py
new file mode 100644
index 00000000..3afdef22
--- /dev/null
+++ b/add_advanced_objects/copy2.py
@@ -0,0 +1,288 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see http://www.gnu.org/licenses/
+# or write to the Free Software Foundation, Inc., 51 Franklin Street,
+# Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENSE BLOCK *****
+
+bl_info = {
+ "name": "Copy2 vertices, edges or faces",
+ "author": "Eleanor Howick (elfnor.com)",
+ "version": (0, 1),
+ "blender": (2, 71, 0),
+ "location": "3D View > Object > Copy 2",
+ "description": "Copy one object to the selected vertices, edges or faces of another object",
+ "warning": "",
+ "category": "Object"
+}
+
+import bpy
+
+from mathutils import Vector, Matrix
+
+
+class Copy2(bpy.types.Operator):
+ bl_idname = "mesh.copy2"
+ bl_label = "Copy 2"
+ bl_options = {"REGISTER", "UNDO"}
+
+ obj_list = None
+
+ def obj_list_cb(self, context):
+ return Copy2.obj_list
+
+ def sec_axes_list_cb(self, context):
+ if self.priaxes == 'X':
+ sec_list = [('Y', 'Y', 'Y'), ('Z', 'Z', 'Z')]
+
+ if self.priaxes == 'Y':
+ sec_list = [('X', 'X', 'X'), ('Z', 'Z', 'Z')]
+
+ if self.priaxes == 'Z':
+ sec_list = [('X', 'X', 'X'), ('Y', 'Y', 'Y')]
+ return sec_list
+
+ copytype = bpy.props.EnumProperty(items=(('V', '', 'paste to vertices', 'VERTEXSEL', 0),
+ ('E', '', 'paste to edges', 'EDGESEL', 1),
+ ('F', '', 'paste to faces', 'FACESEL', 2)),
+ description='where to paste to')
+
+ copyfromobject = bpy.props.EnumProperty(items=obj_list_cb, name='Copy from:')
+
+ priaxes = bpy.props.EnumProperty(items=(('X', 'X', 'along X'),
+ ('Y', 'Y', 'along Y'),
+ ('Z', 'Z', 'along Z')),
+ )
+
+ edgescale = bpy.props.BoolProperty(name='Scale to fill edge?')
+
+ secaxes = bpy.props.EnumProperty(items=sec_axes_list_cb, name='Secondary Axis')
+
+ scale = bpy.props.FloatProperty(default=1.0, min=0.0, name='Scale')
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.prop(self, 'copyfromobject')
+ layout.label("to:")
+ layout.prop(self, 'copytype', expand=True)
+ layout.label("primary axis:")
+ layout.prop(self, 'priaxes', expand=True)
+ layout.label("secondary axis:")
+ layout.prop(self, 'secaxes', expand=True)
+ if self.copytype == 'E':
+ layout.prop(self, 'edgescale')
+ if self.edgescale:
+ layout.prop(self, 'scale')
+ return
+
+ def execute(self, context):
+ copytoobject = context.active_object.name
+ axes = self.priaxes + self.secaxes
+ copy_list = copy_to_from(context.scene,
+ bpy.data.objects[copytoobject],
+ bpy.data.objects[self.copyfromobject],
+ self.copytype,
+ axes,
+ self.edgescale,
+ self.scale)
+ return {"FINISHED"}
+
+ def invoke(self, context, event):
+ Copy2.obj_list = [(obj.name, obj.name, obj.name) for obj in bpy.data.objects]
+ return {"FINISHED"}
+# end Copy2 class
+
+#---------------------------------------------------------------------------------------
+
+
+def add_to_menu(self, context):
+ self.layout.operator(Copy2.bl_idname)
+ return
+
+
+#-----------------------------------------------------------------
+
+def copy_to_from(scene, to_obj, from_obj, copymode, axes, edgescale, scale):
+ if copymode == 'V':
+ copy_list = vertex_copy(scene, to_obj, from_obj, axes)
+ if copymode == 'E':
+ copy_list = edge_copy(scene, to_obj, from_obj, axes, edgescale, scale)
+ if copymode == 'F':
+ copy_list = face_copy(scene, to_obj, from_obj, axes)
+ return copy_list
+
+axes_dict = {'XY': (1, 2, 0),
+ 'XZ': (2, 1, 0),
+ 'YX': (0, 2, 1),
+ 'YZ': (2, 0, 1),
+ 'ZX': (0, 1, 2),
+ 'ZY': (1, 0, 2)}
+
+
+def copyto(scene, source_obj, pos, xdir, zdir, axes, scale=None):
+ """
+ copy the source_obj to pos, so its primary axis points in zdir and its
+ secondary axis points in xdir
+ """
+ copy_obj = source_obj.copy()
+ scene.objects.link(copy_obj)
+
+ xdir = xdir.normalized()
+ zdir = zdir.normalized()
+ # rotation first
+ z_axis = zdir
+ x_axis = xdir
+ y_axis = z_axis.cross(x_axis)
+ # use axes_dict to assign the axis as chosen in panel
+ A, B, C = axes_dict[axes]
+ rot_mat = Matrix()
+ rot_mat[A].xyz = x_axis
+ rot_mat[B].xyz = y_axis
+ rot_mat[C].xyz = z_axis
+ rot_mat.transpose()
+
+ # rotate object
+ copy_obj.matrix_world = rot_mat
+
+ # move object into position
+ copy_obj.location = pos
+
+ # scale object
+ if scale != None:
+ copy_obj.scale = scale
+
+ return copy_obj
+
+
+def vertex_copy(scene, obj, source_obj, axes):
+ # vertex select mode
+ sel_verts = []
+ copy_list = []
+ for v in obj.data.vertices:
+ if v.select == True:
+ sel_verts.append(v)
+
+ # make a set for each vertex. The set contains all the connected vertices
+ # use sets so the list is unique
+ vert_con = [set() for i in range(len(obj.data.vertices))]
+ for e in obj.data.edges:
+ vert_con[e.vertices[0]].add(e.vertices[1])
+ vert_con[e.vertices[1]].add(e.vertices[0])
+
+ for v in sel_verts:
+ pos = v.co * obj.matrix_world.transposed()
+ xco = obj.data.vertices[list(vert_con[v.index])[0]].co * obj.matrix_world.transposed()
+
+ zdir = (v.co + v.normal) * obj.matrix_world.transposed() - pos
+ zdir = zdir.normalized()
+
+ edir = pos - xco
+
+ # edir is nor perpendicular to z dir
+ # want xdir to be projection of edir onto plane through pos with direction zdir
+ xdir = edir - edir.dot(zdir) * zdir
+ xdir = -xdir.normalized()
+
+ copy = copyto(scene, source_obj, pos, xdir, zdir, axes)
+ copy_list.append(copy)
+ # select all copied objects
+ for copy in copy_list:
+ copy.select = True
+ obj.select = False
+ return copy_list
+
+
+def edge_copy(scene, obj, source_obj, axes, es, scale):
+ # edge select mode
+ sel_edges = []
+ copy_list = []
+ for e in obj.data.edges:
+ if e.select == True:
+ sel_edges.append(e)
+ for e in sel_edges:
+ # pos is average of two edge vertexs
+ v0 = obj.data.vertices[e.vertices[0]].co * obj.matrix_world.transposed()
+ v1 = obj.data.vertices[e.vertices[1]].co * obj.matrix_world.transposed()
+ pos = (v0 + v1) / 2
+ # xdir is along edge
+ xdir = v0 - v1
+ xlen = xdir.magnitude
+ xdir = xdir.normalized()
+ # project each edge vertex normal onto plane normal to xdir
+ vn0 = (obj.data.vertices[e.vertices[0]].co * obj.matrix_world.transposed()
+ + obj.data.vertices[e.vertices[0]].normal) - v0
+ vn1 = (obj.data.vertices[e.vertices[1]].co * obj.matrix_world.transposed()
+ + obj.data.vertices[e.vertices[1]].normal) - v1
+ vn0p = vn0 - vn0.dot(xdir) * xdir
+ vn1p = vn1 - vn1.dot(xdir) * xdir
+ # the mean of the two projected normals is the zdir
+ zdir = vn0p + vn1p
+ zdir = zdir.normalized()
+ escale = None
+ if es:
+ escale = Vector([1.0, 1.0, 1.0])
+ i = list('XYZ').index(axes[1])
+ escale[i] = scale * xlen / source_obj.dimensions[i]
+
+ copy = copyto(scene, source_obj, pos, xdir, zdir, axes, scale=escale)
+ copy_list.append(copy)
+ # select all copied objects
+ for copy in copy_list:
+ copy.select = True
+ obj.select = False
+ return copy_list
+
+
+def face_copy(scene, obj, source_obj, axes):
+ # face select mode
+ sel_faces = []
+ copy_list = []
+ for f in obj.data.polygons:
+ if f.select == True:
+ sel_faces.append(f)
+ for f in sel_faces:
+ fco = f.center * obj.matrix_world.transposed()
+ # get first vertex corner of transformed object
+ vco = obj.data.vertices[f.vertices[0]].co * obj.matrix_world.transposed()
+ # get face normal of transformed object
+ fn = (f.center + f.normal) * obj.matrix_world.transposed() - fco
+ fn = fn.normalized()
+
+ copy = copyto(scene, source_obj, fco, vco - fco, fn, axes)
+ copy_list.append(copy)
+ # select all copied objects
+ for copy in copy_list:
+ copy.select = True
+ obj.select = False
+ return copy_list
+
+#-------------------------------------------------------------------
+
+
+def register():
+
+ bpy.utils.register_module(__name__)
+ bpy.types.VIEW3D_MT_object.append(add_to_menu)
+
+
+def unregister():
+
+ bpy.types.VIEW3D_MT_object.remove(add_to_menu)
+ bpy.utils.unregister_module(__name__)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/cubester.py b/add_advanced_objects/cubester.py
new file mode 100644
index 00000000..83053ad1
--- /dev/null
+++ b/add_advanced_objects/cubester.py
@@ -0,0 +1,1133 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# Original Author = Jacob Morris
+# URL = blendingjacob.blogspot.com
+
+bl_info = {
+ "name": "CubeSter",
+ "author": "Jacob Morris",
+ "version": (0, 7),
+ "blender": (2, 78, 0),
+ "location": "View 3D > Toolbar > CubeSter",
+ "description": "Takes image, image sequence, or audio file and converts it "
+ "into a height map based on pixel color and alpha values",
+ "category": "Add Mesh"
+ }
+
+import bpy
+import bmesh
+from bpy.props import (
+ BoolProperty,
+ IntProperty,
+ FloatProperty,
+ StringProperty,
+ EnumProperty,
+ )
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+
+import timeit
+from random import uniform
+from math import radians
+from os import (
+ path,
+ listdir,
+ )
+
+
+# load image if possible
+def adjust_selected_image(self, context):
+ scene = context.scene
+ try:
+ image = bpy.data.images.load(scene.cubester_load_image)
+ scene.cubester_image = image.name
+ except RuntimeError:
+ self.report({"ERROR"}, "CubeSter: '{}' could not be loaded".format(scene.cubester_load_image))
+
+
+# load color image if possible
+def adjust_selected_color_image(self, context):
+ scene = context.scene
+ try:
+ image = bpy.data.images.load(scene.cubester_load_color_image)
+ scene.cubester_color_image = image.name
+ except RuntimeError:
+ self.report({"ERROR"}, "CubeSter: '{}' could not be loaded".format(scene.cubester_load_color_image))
+
+
+# crate block at center position x, y with block width 2 * hx and 2 * hy and height of h
+def create_block(x, y, hw, h, verts: list, faces: list):
+ if bpy.context.scene.cubester_block_style == "size":
+ z = 0.0
+ else:
+ z = h
+ h = 2 * hw
+
+ p = len(verts)
+ verts += [(x - hw, y - hw, z), (x + hw, y - hw, z), (x + hw, y + hw, z), (x - hw, y + hw, z)]
+ verts += [(x - hw, y - hw, z + h), (x + hw, y - hw, z + h),
+ (x + hw, y + hw, z + h), (x - hw, y + hw, z + h)]
+
+ faces += [(p, p + 1, p + 5, p + 4), (p + 1, p + 2, p + 6, p + 5),
+ (p + 2, p + 3, p + 7, p + 6), (p, p + 4, p + 7, p + 3),
+ (p + 4, p + 5, p + 6, p + 7), (p, p + 3, p + 2, p + 1)]
+
+
+# go through all frames in len(frames), adjusting values at frames[x][y]
+def create_f_curves(mesh, frames, frame_step_size, style):
+ # use data to animate mesh
+ action = bpy.data.actions.new("CubeSterAnimation")
+
+ mesh.animation_data_create()
+ mesh.animation_data.action = action
+
+ data_path = "vertices[%d].co"
+
+ vert_index = 4 if style == "blocks" else 0 # index of first vertex
+
+ # loop for every face height value
+ for frame_start_vert in range(len(frames[0])):
+ # only go once if plane, otherwise do all four vertices that are in top plane if blocks
+ end_point = frame_start_vert + 4 if style == "blocks" else frame_start_vert + 1
+
+ # loop through to get the four vertices that compose the face
+ for frame_vert in range(frame_start_vert, end_point):
+ # fcurves for x, y, z
+ fcurves = [action.fcurves.new(data_path % vert_index, i) for i in range(3)]
+ frame_counter = 0 # go through each frame and add position
+ temp_v = mesh.vertices[vert_index].co
+
+ # loop through frames
+ for frame in frames:
+ # new x, y, z positions
+ vals = [temp_v[0], temp_v[1], frame[frame_start_vert]]
+ for i in range(3): # for each x, y, z set each corresponding fcurve
+ fcurves[i].keyframe_points.insert(frame_counter, vals[i], {'FAST'})
+
+ frame_counter += frame_step_size # skip frames for smoother animation
+
+ vert_index += 1
+
+ # only skip vertices if made of blocks
+ if style == "blocks":
+ vert_index += 4
+
+
+# create material with given name, apply to object
+def create_material(scene, ob, name):
+ mat = bpy.data.materials.new("CubeSter_" + name)
+
+ # image
+ if not scene.cubester_use_image_color and scene.cubester_color_image in bpy.data.images:
+ image = bpy.data.images[scene.cubester_color_image]
+ else:
+ image = bpy.data.images[scene.cubester_image]
+
+ if scene.render.engine == "CYCLES":
+ mat.use_nodes = True
+ nodes = mat.node_tree.nodes
+
+ att = nodes.new("ShaderNodeAttribute")
+ att.attribute_name = "Col"
+ att.location = (-200, 300)
+
+ att = nodes.new("ShaderNodeTexImage")
+ att.image = image
+
+ if scene.cubester_load_type == "multiple":
+ att.image.source = "SEQUENCE"
+ att.location = (-200, 700)
+
+ att = nodes.new("ShaderNodeTexCoord")
+ att.location = (-450, 600)
+
+ if scene.cubester_materials == "image":
+ mat.node_tree.links.new(
+ nodes["Image Texture"].outputs[0],
+ nodes["Diffuse BSDF"].inputs[0]
+ )
+ mat.node_tree.links.new(
+ nodes["Texture Coordinate"].outputs[2],
+ nodes["Image Texture"].inputs[0]
+ )
+ else:
+ mat.node_tree.links.new(
+ nodes["Attribute"].outputs[0],
+ nodes["Diffuse BSDF"].inputs[0]
+ )
+ else:
+ if scene.cubester_materials == "image" or scene.render.engine != "BLENDER_RENDER":
+ tex = bpy.data.textures.new("CubeSter_" + name, "IMAGE")
+ tex.image = image
+ slot = mat.texture_slots.add()
+ slot.texture = tex
+ else:
+ mat.use_vertex_color_paint = True
+
+ ob.data.materials.append(mat)
+
+
+# generate mesh from audio
+def create_mesh_from_audio(self, scene, verts, faces):
+ audio_filepath = scene.cubester_audio_path
+ width = scene.cubester_audio_width_blocks
+ length = scene.cubester_audio_length_blocks
+ size_per_hundred = scene.cubester_size_per_hundred_pixels
+
+ size = size_per_hundred / 100
+
+ # create all blocks
+ y = -(width / 2) * size + (size / 2)
+ for r in range(width):
+ x = -(length / 2) * size + (size / 2)
+ for c in range(length):
+ create_block(x, y, size / 2, 1, verts, faces)
+
+ x += size
+ y += size
+
+ # create object
+ mesh = bpy.data.meshes.new("cubed")
+ mesh.from_pydata(verts, [], faces)
+ ob = bpy.data.objects.new("cubed", mesh)
+ bpy.context.scene.objects.link(ob)
+ bpy.context.scene.objects.active = ob
+ ob.select = True
+
+ # inital vertex colors
+ if scene.cubester_materials == "image":
+ picture = bpy.data.images[scene.cubester_color_image]
+ pixels = list(picture.pixels)
+ vert_colors = []
+
+ skip_y = int(picture.size[1] / width)
+ skip_x = int(picture.size[0] / length)
+
+ for row in range(0, picture.size[1], skip_y + 1):
+ # go through each column, step by appropriate amount
+ for column in range(0, picture.size[0] * 4, 4 + skip_x * 4):
+ r, g, b, a = get_pixel_values(picture, pixels, row, column)
+ vert_colors += [(r, g, b) for i in range(24)]
+
+ bpy.ops.mesh.vertex_color_add()
+ i = 0
+ for c in ob.data.vertex_colors[0].data:
+ c.color = vert_colors[i]
+ i += 1
+
+ # image sequence handling
+ if scene.cubester_load_type == "multiple":
+ images = find_sequence_images(self, bpy.context)
+
+ frames_vert_colors = []
+
+ max_images = scene.cubester_max_images + 1 if \
+ len(images[0]) > scene.cubester_max_images else len(images[0])
+
+ # goes through and for each image for each block finds new height
+ for image_index in range(0, max_images, scene.cubester_skip_images):
+ filepath = images[0][image_index]
+ name = images[1][image_index]
+ picture = fetch_image(self, name, filepath)
+ pixels = list(picture.pixels)
+
+ frame_colors = []
+
+ for row in range(0, picture.size[1], skip_y + 1):
+ for column in range(0, picture.size[0] * 4, 4 + skip_x * 4):
+ r, g, b, a = get_pixel_values(picture, pixels, row, column)
+ frame_colors += [(r, g, b) for i in range(24)]
+
+ frames_vert_colors.append(frame_colors)
+
+ scene.cubester_vertex_colors[ob.name] = {"type": "vertex", "frames": frames_vert_colors,
+ "frame_skip": scene.cubester_frame_step,
+ "total_images": max_images}
+
+ # either add material or create
+ if ("CubeSter_" + "Vertex") in bpy.data.materials:
+ ob.data.materials.append(bpy.data.materials["CubeSter_" + "Vertex"])
+ else:
+ create_material(scene, ob, "Vertex")
+
+ # set keyframe for each object as initial point
+ frame = [1 for i in range(int(len(verts) / 8))]
+ frames = [frame]
+
+ area = bpy.context.area
+ old_type = area.type
+ area.type = "GRAPH_EDITOR"
+
+ scene.frame_current = 0
+
+ create_f_curves(mesh, frames, 1, "blocks")
+
+ # deselect all fcurves
+ fcurves = ob.data.animation_data.action.fcurves.data.fcurves
+ for i in fcurves:
+ i.select = False
+
+ max_images = scene.cubester_audio_max_freq
+ min_freq = scene.cubester_audio_min_freq
+ freq_frame = scene.cubester_audio_offset_type
+
+ freq_step = (max_images - min_freq) / length
+ freq_sub_step = freq_step / width
+
+ frame_step = scene.cubester_audio_frame_offset
+
+ # animate each block with a portion of the frequency
+ for c in range(length):
+ frame_off = 0
+ for r in range(width):
+ if freq_frame == "frame":
+ scene.frame_current = frame_off
+ l = c * freq_step
+ h = (c + 1) * freq_step
+ frame_off += frame_step
+ else:
+ l = c * freq_step + (r * freq_sub_step)
+ h = c * freq_step + ((r + 1) * freq_sub_step)
+
+ pos = c + (r * length) # block number
+ index = pos * 4 # first index for vertex
+
+ # select curves
+ for i in range(index, index + 4):
+ curve = i * 3 + 2 # fcurve location
+ fcurves[curve].select = True
+
+ bpy.ops.graph.sound_bake(filepath=bpy.path.abspath(audio_filepath), low=l, high=h)
+
+ # deselect curves
+ for i in range(index, index + 4):
+ curve = i * 3 + 2 # fcurve location
+ fcurves[curve].select = False
+
+ area.type = old_type
+
+ # UV unwrap
+ create_uv_map(bpy.context, width, length)
+
+ # if radial apply needed modifiers
+ if scene.cubester_audio_block_layout == "radial":
+ # add bezier curve of correct width
+ bpy.ops.curve.primitive_bezier_circle_add()
+ curve = bpy.context.object
+ # slope determined off of collected data
+ curve_size = (0.319 * (width * (size * 100)) - 0.0169) / 100
+ curve.dimensions = (curve_size, curve_size, 0.0)
+ # correct for z height
+ curve.scale = (curve.scale[0], curve.scale[0], curve.scale[0])
+
+ ob.select = True
+ curve.select = False
+ scene.objects.active = ob
+
+ # data was collected and then multi-variable regression was done in Excel
+ # influence of width and length
+ width_infl, length_infl, intercept = -0.159125, 0.49996, 0.007637
+ x_offset = ((width * (size * 100) * width_infl) +
+ (length * (size * 100) * length_infl) + intercept) / 100
+ ob.location = (ob.location[0] + x_offset, ob.location[1], ob.location[2])
+
+ ob.rotation_euler = (radians(-90), 0.0, 0.0)
+ bpy.ops.object.modifier_add(type="CURVE")
+ ob.modifiers["Curve"].object = curve
+ ob.modifiers["Curve"].deform_axis = "POS_Z"
+
+
+# generate mesh from image(s)
+def create_mesh_from_image(self, scene, verts, faces):
+ context = bpy.context
+ picture = bpy.data.images[scene.cubester_image]
+ pixels = list(picture.pixels)
+
+ x_pixels = picture.size[0] / (scene.cubester_skip_pixels + 1)
+ y_pixels = picture.size[1] / (scene.cubester_skip_pixels + 1)
+
+ width = x_pixels / 100 * scene.cubester_size_per_hundred_pixels
+ height = y_pixels / 100 * scene.cubester_size_per_hundred_pixels
+
+ step = width / x_pixels
+ half_width = step / 2
+
+ y = -height / 2 + half_width
+
+ vert_colors = []
+ weights = [uniform(0.0, 1.0) for i in range(4)] # random weights
+ rows = 0
+
+ # go through each row of pixels stepping by scene.cubester_skip_pixels + 1
+ for row in range(0, picture.size[1], scene.cubester_skip_pixels + 1):
+ rows += 1
+ x = -width / 2 + half_width # reset to left edge of mesh
+ # go through each column, step by appropriate amount
+ for column in range(0, picture.size[0] * 4, 4 + scene.cubester_skip_pixels * 4):
+ r, g, b, a = get_pixel_values(picture, pixels, row, column)
+ h = find_point_height(r, g, b, a, scene)
+
+ # if not transparent
+ if h != -1:
+ if scene.cubester_mesh_style == "blocks":
+ create_block(x, y, half_width, h, verts, faces)
+ vert_colors += [(r, g, b) for i in range(24)]
+ else:
+ verts += [(x, y, h)]
+ vert_colors += [(r, g, b) for i in range(4)]
+
+ x += step
+ y += step
+
+ # if plane not blocks, then remove last 4 items from vertex_colors
+ # as the faces have already wrapped around
+ if scene.cubester_mesh_style == "plane":
+ del vert_colors[len(vert_colors) - 4:len(vert_colors)]
+
+ # create faces if plane based and not block based
+ if scene.cubester_mesh_style == "plane":
+ off = int(len(verts) / rows)
+ for r in range(rows - 1):
+ for c in range(off - 1):
+ faces += [(r * off + c, r * off + c + 1, (r + 1) * off + c + 1, (r + 1) * off + c)]
+
+ mesh = bpy.data.meshes.new("cubed")
+ mesh.from_pydata(verts, [], faces)
+ ob = bpy.data.objects.new("cubed", mesh)
+ context.scene.objects.link(ob)
+ context.scene.objects.active = ob
+ ob.select = True
+
+ # uv unwrap
+ if scene.cubester_mesh_style == "blocks":
+ create_uv_map(context, rows, int(len(faces) / 6 / rows))
+ else:
+ create_uv_map(context, rows - 1, int(len(faces) / (rows - 1)))
+
+ # material
+ # determine name and if already created
+ if scene.cubester_materials == "vertex": # vertex color
+ image_name = "Vertex"
+ elif not scene.cubester_use_image_color and scene.cubester_color_image in bpy.data.images and \
+ scene.cubester_materials == "image": # replaced image
+ image_name = scene.cubester_color_image
+ else: # normal image
+ image_name = scene.cubester_image
+
+ # either add material or create
+ if ("CubeSter_" + image_name) in bpy.data.materials:
+ ob.data.materials.append(bpy.data.materials["CubeSter_" + image_name])
+
+ # create material
+ else:
+ create_material(scene, ob, image_name)
+
+ # vertex colors
+ bpy.ops.mesh.vertex_color_add()
+ i = 0
+ for c in ob.data.vertex_colors[0].data:
+ c.color = vert_colors[i]
+ i += 1
+
+ frames = []
+ # image sequence handling
+ if scene.cubester_load_type == "multiple":
+ images = find_sequence_images(self, context)
+ frames_vert_colors = []
+
+ max_images = scene.cubester_max_images + 1 if \
+ len(images[0]) > scene.cubester_max_images else len(images[0])
+
+ # goes through and for each image for each block finds new height
+ for image_index in range(0, max_images, scene.cubester_skip_images):
+ filepath = images[0][image_index]
+ name = images[1][image_index]
+ picture = fetch_image(self, name, filepath)
+ pixels = list(picture.pixels)
+
+ frame_heights = []
+ frame_colors = []
+
+ for row in range(0, picture.size[1], scene.cubester_skip_pixels + 1):
+ for column in range(0, picture.size[0] * 4, 4 + scene.cubester_skip_pixels * 4):
+ r, g, b, a = get_pixel_values(picture, pixels, row, column)
+ h = find_point_height(r, g, b, a, scene)
+
+ if h != -1:
+ frame_heights.append(h)
+ if scene.cubester_mesh_style == "blocks":
+ frame_colors += [(r, g, b) for i in range(24)]
+ else:
+ frame_colors += [(r, g, b) for i in range(4)]
+
+ if scene.cubester_mesh_style == "plane":
+ del vert_colors[len(vert_colors) - 4:len(vert_colors)]
+
+ frames.append(frame_heights)
+ frames_vert_colors.append(frame_colors)
+
+ # determine what data to use
+ if scene.cubester_materials == "vertex" or scene.render.engine == "BLENDER_ENGINE":
+ scene.cubester_vertex_colors[ob.name] = {
+ "type": "vertex", "frames": frames_vert_colors,
+ "frame_skip": scene.cubester_frame_step,
+ "total_images": max_images
+ }
+ else:
+ scene.cubester_vertex_colors[ob.name] = {
+ "type": "image", "frame_skip": scene.cubester_frame_step,
+ "total_images": max_images
+ }
+ att = get_image_node(ob.data.materials[0])
+ att.image_user.frame_duration = len(frames) * scene.cubester_frame_step
+
+ # animate mesh
+ create_f_curves(mesh, frames, scene.cubester_frame_step, scene.cubester_mesh_style)
+
+
+# generate uv map for object
+def create_uv_map(context, rows, columns):
+ mesh = context.object.data
+ mesh.uv_textures.new("cubester")
+ bm = bmesh.new()
+ bm.from_mesh(mesh)
+
+ uv_layer = bm.loops.layers.uv[0]
+ bm.faces.ensure_lookup_table()
+
+ x_scale = 1 / columns
+ y_scale = 1 / rows
+
+ y_pos = 0.0
+ x_pos = 0.0
+ count = columns - 1 # hold current count to compare to if need to go to next row
+
+ # if blocks
+ if context.scene.cubester_mesh_style == "blocks":
+ for fa in range(int(len(bm.faces) / 6)):
+ for i in range(6):
+ pos = (fa * 6) + i
+ bm.faces[pos].loops[0][uv_layer].uv = (x_pos, y_pos)
+ bm.faces[pos].loops[1][uv_layer].uv = (x_pos + x_scale, y_pos)
+ bm.faces[pos].loops[2][uv_layer].uv = (x_pos + x_scale, y_pos + y_scale)
+ bm.faces[pos].loops[3][uv_layer].uv = (x_pos, y_pos + y_scale)
+
+ x_pos += x_scale
+
+ if fa >= count:
+ y_pos += y_scale
+ x_pos = 0.0
+ count += columns
+
+ # if planes
+ else:
+ for fa in range(len(bm.faces)):
+ bm.faces[fa].loops[0][uv_layer].uv = (x_pos, y_pos)
+ bm.faces[fa].loops[1][uv_layer].uv = (x_pos + x_scale, y_pos)
+ bm.faces[fa].loops[2][uv_layer].uv = (x_pos + x_scale, y_pos + y_scale)
+ bm.faces[fa].loops[3][uv_layer].uv = (x_pos, y_pos + y_scale)
+
+ x_pos += x_scale
+
+ if fa >= count:
+ y_pos += y_scale
+ x_pos = 0.0
+ count += columns
+
+ bm.to_mesh(mesh)
+
+
+# returns length in frames
+def find_audio_length(self, context):
+ audio_file = context.scene.cubester_audio_path
+ length = 0
+
+ if audio_file != "":
+ # confirm that strip hasn't been loaded yet
+ for strip in context.scene.sequence_editor.sequences_all:
+ if type(strip) == bpy.types.SoundSequence and strip.sound.filepath == audio_file:
+ length = strip.frame_final_duration
+
+ if length == 0:
+ area = context.area
+ old_type = area.type
+ area.type = "SEQUENCE_EDITOR"
+
+ bpy.ops.sequencer.sound_strip_add(filepath=audio_file)
+ area.type = old_type
+
+ # find audio file
+ for strip in context.scene.sequence_editor.sequences_all:
+ if type(strip) == bpy.types.SoundSequence and strip.sound.filepath == audio_file:
+ length = strip.frame_final_duration
+
+ context.scene.cubester_audio_file_length = str(length)
+
+
+# if already loaded return image, else load and return
+def fetch_image(self, name, load_path):
+ if name in bpy.data.images:
+ return bpy.data.images[name]
+ else:
+ try:
+ image = bpy.data.images.load(load_path)
+ return image
+ except RuntimeError:
+ self.report({"ERROR"}, "CubeSter: '{}' could not be loaded".format(load_path))
+ return None
+
+
+# find height for point
+def find_point_height(r, g, b, a, scene):
+ if a: # if not completely transparent
+ normalize = 1
+
+ # channel weighting
+ if not scene.cubester_advanced:
+ composed = 0.25 * r + 0.25 * g + 0.25 * b + 0.25 * a
+ else:
+ # user defined weighting
+ if not scene.cubester_random_weights:
+ composed = scene.cubester_weight_r * r + scene.cubester_weight_g * g + \
+ scene.cubester_weight_b * b + scene.cubester_weight_a * a
+ total = scene.cubester_weight_r + scene.cubester_weight_g + scene.cubester_weight_b + \
+ scene.cubester_weight_a
+
+ normalize = 1 / total
+ # random weighting
+ else:
+ weights = [uniform(0.0, 1.0) for i in range(4)]
+ composed = weights[0] * r + weights[1] * g + weights[2] * b + weights[3] * a
+ total = weights[0] + weights[1] + weights[2] + weights[3]
+ normalize = 1 / total
+
+ if scene.cubester_invert:
+ h = (1 - composed) * scene.cubester_height_scale * normalize
+ else:
+ h = composed * scene.cubester_height_scale * normalize
+
+ return h
+ else:
+ return -1
+
+
+# find all images that would belong to sequence
+def find_sequence_images(self, context):
+ scene = context.scene
+ images = [[], []]
+
+ if scene.cubester_image in bpy.data.images:
+ image = bpy.data.images[scene.cubester_image]
+ main = image.name.split(".")[0]
+
+ # first part of name to check against other files
+ length = len(main)
+ keep_going = True
+ for i in range(length - 1, -1, -1):
+ if main[i].isdigit() and keep_going:
+ length -= 1
+ else:
+ keep_going = not keep_going
+ name = main[0:length]
+
+ dir_name = path.dirname(bpy.path.abspath(image.filepath))
+
+ try:
+ for file in listdir(dir_name):
+ if path.isfile(path.join(dir_name, file)) and file.startswith(name):
+ images[0].append(path.join(dir_name, file))
+ images[1].append(file)
+ except FileNotFoundError:
+ self.report({"ERROR"}, "CubeSter: '{}' directory not found".format(dir_name))
+
+ return images
+
+
+# get image node
+def get_image_node(mat):
+ nodes = mat.node_tree.nodes
+ att = nodes["Image Texture"]
+
+ return att
+
+
+# get the RGBA values from pixel
+def get_pixel_values(picture, pixels, row, column):
+ # determine i position to start at based on row and column position
+ i = (row * picture.size[0] * 4) + column
+ pixs = pixels[i: i + 4]
+ r = pixs[0]
+ g = pixs[1]
+ b = pixs[2]
+ a = pixs[3]
+
+ return r, g, b, a
+
+
+# frame change handler for materials
+def material_frame_handler(scene):
+ frame = scene.frame_current
+
+ keys = list(scene.cubester_vertex_colors.keys())
+
+ # get keys and see if object is still in scene
+ for i in keys:
+ # if object is in scene then update information
+ if i in bpy.data.objects:
+ ob = bpy.data.objects[i]
+ data = scene.cubester_vertex_colors[ob.name]
+ skip_frames = data["frame_skip"]
+
+ # update materials using vertex colors
+ if data['type'] == "vertex":
+ colors = data["frames"]
+
+ if frame % skip_frames == 0 and 0 <= frame < (data['total_images'] - 1) * skip_frames:
+ use_frame = int(frame / skip_frames)
+ color = colors[use_frame]
+
+ i = 0
+ for c in ob.data.vertex_colors[0].data:
+ c.color = color[i]
+ i += 1
+
+ else:
+ att = get_image_node(ob.data.materials[0])
+ offset = frame - int(frame / skip_frames)
+ att.image_user.frame_offset = -offset
+
+ # if the object is no longer in the scene then delete then entry
+ else:
+ del scene.cubester_vertex_colors[i]
+
+
+# main properties
+bpy.types.Scene.cubester_audio_image = EnumProperty(
+ name="Input Type",
+ items=(("image", "Image", ""),
+ ("audio", "Audio", ""))
+ )
+bpy.types.Scene.cubester_audio_file_length = StringProperty(
+ default=""
+ )
+# audio
+bpy.types.Scene.cubester_audio_path = StringProperty(
+ default="",
+ name="Audio File",
+ subtype="FILE_PATH",
+ update=find_audio_length
+ )
+bpy.types.Scene.cubester_audio_min_freq = IntProperty(
+ name="Minimum Frequency",
+ min=20, max=100000,
+ default=20
+ )
+bpy.types.Scene.cubester_audio_max_freq = IntProperty(
+ name="Maximum Frequency",
+ min=21, max=999999,
+ default=5000
+ )
+bpy.types.Scene.cubester_audio_offset_type = EnumProperty(
+ name="Offset Type",
+ items=(("freq", "Frequency Offset", ""),
+ ("frame", "Frame Offset", "")),
+ description="Type of offset per row of mesh"
+ )
+bpy.types.Scene.cubester_audio_frame_offset = IntProperty(
+ name="Frame Offset",
+ min=0, max=10,
+ default=2
+ )
+bpy.types.Scene.cubester_audio_block_layout = EnumProperty(
+ name="Block Layout",
+ items=(("rectangle", "Rectangular", ""),
+ ("radial", "Radial", ""))
+ )
+bpy.types.Scene.cubester_audio_width_blocks = IntProperty(
+ name="Width Block Count",
+ min=1, max=10000,
+ default=5
+ )
+bpy.types.Scene.cubester_audio_length_blocks = IntProperty(
+ name="Length Block Count",
+ min=1, max=10000,
+ default=50
+ )
+# image
+bpy.types.Scene.cubester_load_type = EnumProperty(
+ name="Image Input Type",
+ items=(("single", "Single Image", ""),
+ ("multiple", "Image Sequence", ""))
+ )
+bpy.types.Scene.cubester_image = StringProperty(
+ default="",
+ name=""
+ )
+bpy.types.Scene.cubester_load_image = StringProperty(
+ default="",
+ name="Load Image",
+ subtype="FILE_PATH",
+ update=adjust_selected_image
+ )
+bpy.types.Scene.cubester_skip_images = IntProperty(
+ name="Image Step",
+ min=1, max=30,
+ default=1,
+ description="Step from image to image by this number"
+ )
+bpy.types.Scene.cubester_max_images = IntProperty(
+ name="Max Number Of Images",
+ min=2, max=1000,
+ default=10,
+ description="Maximum number of images to be used"
+ )
+bpy.types.Scene.cubester_frame_step = IntProperty(
+ name="Frame Step Size",
+ min=1, max=10,
+ default=4,
+ description="The number of frames each picture is used"
+ )
+bpy.types.Scene.cubester_skip_pixels = IntProperty(
+ name="Skip # Pixels",
+ min=0, max=256,
+ default=64,
+ description="Skip this number of pixels before placing the next"
+ )
+bpy.types.Scene.cubester_mesh_style = EnumProperty(
+ name="Mesh Type",
+ items=(("blocks", "Blocks", ""),
+ ("plane", "Plane", "")),
+ description="Compose mesh of multiple blocks or of a single plane"
+ )
+bpy.types.Scene.cubester_block_style = EnumProperty(
+ name="Block Style",
+ items=(("size", "Vary Size", ""),
+ ("position", "Vary Position", "")),
+ description="Vary Z-size of block, or vary Z-position"
+ )
+bpy.types.Scene.cubester_height_scale = FloatProperty(
+ name="Height Scale",
+ subtype="DISTANCE",
+ min=0.1, max=2,
+ default=0.2
+ )
+bpy.types.Scene.cubester_invert = BoolProperty(
+ name="Invert Height?",
+ default=False
+ )
+# general adjustments
+bpy.types.Scene.cubester_size_per_hundred_pixels = FloatProperty(
+ name="Size Per 100 Blocks/Points",
+ subtype="DISTANCE",
+ min=0.001, max=5,
+ default=1
+ )
+# material based stuff
+bpy.types.Scene.cubester_materials = EnumProperty(
+ name="Material",
+ items=(("vertex", "Vertex Colors", ""),
+ ("image", "Image", "")),
+ description="Color with vertex colors, or uv unwrap and use an image"
+ )
+bpy.types.Scene.cubester_use_image_color = BoolProperty(
+ name="Use Original Image Colors'?",
+ default=True,
+ description="Use original image colors, or replace with other"
+ )
+bpy.types.Scene.cubester_color_image = StringProperty(
+ default="", name=""
+ )
+bpy.types.Scene.cubester_load_color_image = StringProperty(
+ default="",
+ name="Load Color Image",
+ subtype="FILE_PATH",
+ update=adjust_selected_color_image
+ )
+bpy.types.Scene.cubester_vertex_colors = {}
+# advanced
+bpy.types.Scene.cubester_advanced = BoolProperty(
+ name="Advanced Options?"
+ )
+bpy.types.Scene.cubester_random_weights = BoolProperty(
+ name="Random Weights?"
+ )
+bpy.types.Scene.cubester_weight_r = FloatProperty(
+ name="Red",
+ subtype="FACTOR",
+ min=0.01, max=1.0,
+ default=0.25
+ )
+bpy.types.Scene.cubester_weight_g = FloatProperty(
+ name="Green",
+ subtype="FACTOR",
+ min=0.01, max=1.0,
+ default=0.25
+ )
+bpy.types.Scene.cubester_weight_b = FloatProperty(
+ name="Blue",
+ subtype="FACTOR",
+ min=0.01, max=1.0,
+ default=0.25
+ )
+bpy.types.Scene.cubester_weight_a = FloatProperty(
+ name="Alpha",
+ subtype="FACTOR",
+ min=0.01, max=1.0,
+ default=0.25
+ )
+
+
+class CubeSterPanel(Panel):
+ bl_idname = "OBJECT_PT.cubester"
+ bl_label = "CubeSter"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_category = "Create"
+ bl_options = {"DEFAULT_CLOSED"}
+ bl_context = "objectmode"
+
+ def draw(self, context):
+ layout = self.layout.box()
+ scene = bpy.context.scene
+ images_found = 0
+ rows = 0
+ columns = 0
+
+ layout.prop(scene, "cubester_audio_image", icon="IMAGE_COL")
+ layout.separator()
+
+ if scene.cubester_audio_image == "image":
+ box = layout.box()
+ box.prop(scene, "cubester_load_type")
+ box.label("Image To Convert:")
+ box.prop_search(scene, "cubester_image", bpy.data, "images")
+ box.prop(scene, "cubester_load_image")
+ layout.separator()
+
+ # find number of approriate images if sequence
+ if scene.cubester_load_type == "multiple":
+ box = layout.box()
+ # display number of images found there
+ images = find_sequence_images(self, context)
+ images_found = len(images[0]) if len(images[0]) <= scene.cubester_max_images \
+ else scene.cubester_max_images
+
+ if len(images[0]):
+ box.label(str(len(images[0])) + " Images Found", icon="PACKAGE")
+
+ box.prop(scene, "cubester_max_images")
+ box.prop(scene, "cubester_skip_images")
+ box.prop(scene, "cubester_frame_step")
+
+ layout.separator()
+
+ box = layout.box()
+ box.prop(scene, "cubester_skip_pixels")
+ box.prop(scene, "cubester_size_per_hundred_pixels")
+ box.prop(scene, "cubester_height_scale")
+ box.prop(scene, "cubester_invert", icon="FILE_REFRESH")
+
+ layout.separator()
+ box = layout.box()
+ box.prop(scene, "cubester_mesh_style", icon="MESH_GRID")
+
+ if scene.cubester_mesh_style == "blocks":
+ box.prop(scene, "cubester_block_style")
+
+ # audio file
+ else:
+ layout.prop(scene, "cubester_audio_path")
+ layout.separator()
+ box = layout.box()
+
+ box.prop(scene, "cubester_audio_min_freq")
+ box.prop(scene, "cubester_audio_max_freq")
+ box.separator()
+ box.prop(scene, "cubester_audio_offset_type")
+
+ if scene.cubester_audio_offset_type == "frame":
+ box.prop(scene, "cubester_audio_frame_offset")
+ box.separator()
+
+ box.prop(scene, "cubester_audio_block_layout")
+ box.prop(scene, "cubester_audio_width_blocks")
+ box.prop(scene, "cubester_audio_length_blocks")
+
+ rows = scene.cubester_audio_width_blocks
+ columns = scene.cubester_audio_length_blocks
+
+ box.prop(scene, "cubester_size_per_hundred_pixels")
+
+ # materials
+ layout.separator()
+ box = layout.box()
+
+ box.prop(scene, "cubester_materials", icon="MATERIAL")
+
+ if scene.cubester_materials == "image":
+ box.prop(scene, "cubester_load_type")
+
+ # find number of approriate images if sequence
+ if scene.cubester_load_type == "multiple":
+ # display number of images found there
+ images = find_sequence_images(self, context)
+ images_found = len(images[0]) if len(images[0]) <= scene.cubester_max_images \
+ else scene.cubester_max_images
+
+ if len(images[0]):
+ box.label(str(len(images[0])) + " Images Found", icon="PACKAGE")
+ box.prop(scene, "cubester_max_images")
+ box.prop(scene, "cubester_skip_images")
+ box.prop(scene, "cubester_frame_step")
+
+ box.separator()
+
+ if scene.cubester_audio_image == "image":
+ box.prop(scene, "cubester_use_image_color", icon="COLOR")
+
+ if not scene.cubester_use_image_color or scene.cubester_audio_image == "audio":
+ box.label("Image To Use For Colors:")
+ box.prop_search(scene, "cubester_color_image", bpy.data, "images")
+ box.prop(scene, "cubester_load_color_image")
+
+ if scene.cubester_image in bpy.data.images:
+ rows = int(bpy.data.images[scene.cubester_image].size[1] / (scene.cubester_skip_pixels + 1))
+ columns = int(bpy.data.images[scene.cubester_image].size[0] / (scene.cubester_skip_pixels + 1))
+
+ layout.separator()
+ box = layout.box()
+
+ if scene.cubester_mesh_style == "blocks":
+ box.label("Approximate Cube Count: " + str(rows * columns))
+ box.label("Expected # Verts/Faces: " + str(rows * columns * 8) + " / " + str(rows * columns * 6))
+ else:
+ box.label("Approximate Point Count: " + str(rows * columns))
+ box.label("Expected # Verts/Faces: " + str(rows * columns) + " / " + str(rows * (columns - 1)))
+
+ # blocks and plane generation time values
+ if scene.cubester_mesh_style == "blocks":
+ slope = 0.0000876958
+ intercept = 0.02501
+ block_infl, frame_infl, intercept2 = 0.0025934, 0.38507, -0.5840189
+
+ else:
+ slope = 0.000017753
+ intercept = 0.04201
+ block_infl, frame_infl, intercept2 = 0.000619, 0.344636, -0.272759
+
+ # if creating image based mesh
+ points = rows * columns
+ if scene.cubester_audio_image == "image":
+ if scene.cubester_load_type == "single":
+ time = rows * columns * slope + intercept # approximate time count for mesh
+ else:
+ time = (points * slope) + intercept + (points * block_infl) + \
+ (images_found / scene.cubester_skip_images * frame_infl) + intercept2
+
+ box.label("Images To Be Used: " + str(int(images_found / scene.cubester_skip_images)))
+
+ # audio based mesh
+ else:
+ box.label("Audio Track Length: " + scene.cubester_audio_file_length + " frames")
+
+ block_infl, frame_infl, intercept = 0.0948, 0.0687566, -25.85985
+ time = (points * block_infl) + (int(scene.cubester_audio_file_length) * frame_infl) + intercept
+
+ time_mod = "s"
+ if time > 60: # convert to minutes if needed
+ time /= 60
+ time_mod = "min"
+ time = round(time, 3)
+
+ box.label("Expected Time: " + str(time) + " " + time_mod)
+
+ # advanced
+ if scene.cubester_audio_image == "image":
+ layout.separator()
+ box = layout.box()
+ box.prop(scene, "cubester_advanced", icon="TRIA_DOWN")
+ if bpy.context.scene.cubester_advanced:
+ box.prop(scene, "cubester_random_weights", icon="RNDCURVE")
+ box.separator()
+
+ if not bpy.context.scene.cubester_random_weights:
+ box.label("RGBA Channel Weights", icon="COLOR")
+ box.prop(scene, "cubester_weight_r")
+ box.prop(scene, "cubester_weight_g")
+ box.prop(scene, "cubester_weight_b")
+ box.prop(scene, "cubester_weight_a")
+
+ # generate mesh
+ layout.separator()
+ layout.operator("mesh.cubester", icon="OBJECT_DATA")
+
+
+class CubeSter(Operator):
+ bl_idname = "mesh.cubester"
+ bl_label = "Generate Mesh"
+ bl_options = {"REGISTER", "UNDO"}
+
+ def execute(self, context):
+ verts, faces = [], []
+
+ start = timeit.default_timer()
+ scene = bpy.context.scene
+
+ if scene.cubester_audio_image == "image":
+ create_mesh_from_image(self, scene, verts, faces)
+ frames = find_sequence_images(self, context)
+ created = len(frames[0])
+ else:
+ create_mesh_from_audio(self, scene, verts, faces)
+ created = int(scene.cubester_audio_file_length)
+
+ stop = timeit.default_timer()
+
+ if scene.cubester_mesh_style == "blocks" or scene.cubester_audio_image == "audio":
+ self.report({"INFO"},
+ "CubeSter: {} blocks and {} frame(s) "
+ "in {}s".format(str(int(len(verts) / 8)),
+ str(created),
+ str(round(stop - start, 4)))
+ )
+ else:
+ self.report({"INFO"},
+ "CubeSter: {} points and {} frame(s) "
+ "in {}s" .format(str(len(verts)),
+ str(created),
+ str(round(stop - start, 4)))
+ )
+
+ return {"FINISHED"}
+
+
+def register():
+ bpy.utils.register_module(__name__)
+ bpy.app.handlers.frame_change_pre.append(material_frame_handler)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+ bpy.app.handlers.frame_change_pre.remove(material_frame_handler)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/delaunay_voronoi/DelaunayVoronoi.py b/add_advanced_objects/delaunay_voronoi/DelaunayVoronoi.py
new file mode 100644
index 00000000..18d7f38f
--- /dev/null
+++ b/add_advanced_objects/delaunay_voronoi/DelaunayVoronoi.py
@@ -0,0 +1,1000 @@
+# -*- coding: utf-8 -*-
+
+# Voronoi diagram calculator/ Delaunay triangulator
+#
+# - Voronoi Diagram Sweepline algorithm and C code by Steven Fortune,
+# 1987, http://ect.bell-labs.com/who/sjf/
+# - Python translation to file voronoi.py by Bill Simons, 2005, http://www.oxfish.com/
+# - Additional changes for QGIS by Carson Farmer added November 2010
+# - 2012 Ported to Python 3 and additional clip functions by domlysz at gmail.com
+#
+# Calculate Delaunay triangulation or the Voronoi polygons for a set of
+# 2D input points.
+#
+# Derived from code bearing the following notice:
+#
+# The author of this software is Steven Fortune. Copyright (c) 1994 by AT&T
+# Bell Laboratories.
+# Permission to use, copy, modify, and distribute this software for any
+# purpose without fee is hereby granted, provided that this entire notice
+# is included in all copies of any software which is or includes a copy
+# or modification of this software and in all copies of the supporting
+# documentation for such software.
+# THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED
+# WARRANTY. IN PARTICULAR, NEITHER THE AUTHORS NOR AT&T MAKE ANY
+# REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY
+# OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE.
+#
+# Comments were incorporated from Shane O'Sullivan's translation of the
+# original code into C++ (http://mapviewer.skynet.ie/voronoi.html)
+#
+# Steve Fortune's homepage: http://netlib.bell-labs.com/cm/cs/who/sjf/index.html
+#
+# For programmatic use, two functions are available:
+#
+# computeVoronoiDiagram(points, xBuff, yBuff, polygonsOutput=False, formatOutput=False):
+# Takes :
+# - a list of point objects (which must have x and y fields).
+# - x and y buffer values which are the expansion percentages of the
+# bounding box rectangle including all input points.
+# Returns :
+# - With default options :
+# A list of 2-tuples, representing the two points of each Voronoi diagram edge.
+# Each point contains 2-tuples which are the x,y coordinates of point.
+# if formatOutput is True, returns :
+# - a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
+# - and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram.
+# v1 and v2 are the indices of the vertices at the end of the edge.
+# - If polygonsOutput option is True, returns :
+# A dictionary of polygons, keys are the indices of the input points,
+# values contains n-tuples representing the n points of each Voronoi diagram polygon.
+# Each point contains 2-tuples which are the x,y coordinates of point.
+# if formatOutput is True, returns :
+# - A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
+# - and a dictionary of input points indices. Values contains n-tuples representing
+# the n points of each Voronoi diagram polygon.
+# Each tuple contains the vertex indices of the polygon vertices.
+#
+# computeDelaunayTriangulation(points):
+# Takes a list of point objects (which must have x and y fields).
+# Returns a list of 3-tuples: the indices of the points that form a Delaunay triangle.
+
+import bpy
+import math
+import sys
+import getopt
+TOLERANCE = 1e-9
+BIG_FLOAT = 1e38
+
+# TODO : Licence, prints, imports, autorship
+
+
+class Context(object):
+
+ def __init__(self):
+ self.doPrint = 0
+ self.debug = 0
+
+ # tuple (xmin, xmax, ymin, ymax)
+ self.extent = ()
+ self.triangulate = False
+ # list of vertex 2-tuples: (x,y)
+ self.vertices = []
+ # equation of line 3-tuple (a b c), for the equation of the line a*x+b*y = c
+ self.lines = []
+
+ # edge 3-tuple: (line index, vertex 1 index, vertex 2 index)
+ # if either vertex index is -1, the edge extends to infinity
+ self.edges = []
+ # 3-tuple of vertex indices
+ self.triangles = []
+ # a dict of site:[edges] pairs
+ self.polygons = {}
+
+
+# Clip functions #
+ def getClipEdges(self):
+ xmin, xmax, ymin, ymax = self.extent
+ clipEdges = []
+ for edge in self.edges:
+ equation = self.lines[edge[0]] # line equation
+ if edge[1] != -1 and edge[2] != -1: # finite line
+ x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
+ x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
+ pt1, pt2 = (x1, y1), (x2, y2)
+ inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2)
+ if inExtentP1 and inExtentP2:
+ clipEdges.append((pt1, pt2))
+ elif inExtentP1 and not inExtentP2:
+ pt2 = self.clipLine(x1, y1, equation, leftDir=False)
+ clipEdges.append((pt1, pt2))
+ elif not inExtentP1 and inExtentP2:
+ pt1 = self.clipLine(x2, y2, equation, leftDir=True)
+ clipEdges.append((pt1, pt2))
+ else: # infinite line
+ if edge[1] != -1:
+ x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
+ leftDir = False
+ else:
+ x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
+ leftDir = True
+ if self.inExtent(x1, y1):
+ pt1 = (x1, y1)
+ pt2 = self.clipLine(x1, y1, equation, leftDir)
+ clipEdges.append((pt1, pt2))
+ return clipEdges
+
+ def getClipPolygons(self, closePoly):
+ xmin, xmax, ymin, ymax = self.extent
+ poly = {}
+ for inPtsIdx, edges in self.polygons.items():
+ clipEdges = []
+ for edge in edges:
+ equation = self.lines[edge[0]] # line equation
+ if edge[1] != -1 and edge[2] != -1: # finite line
+ x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
+ x2, y2 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
+ pt1, pt2 = (x1, y1), (x2, y2)
+ inExtentP1, inExtentP2 = self.inExtent(x1, y1), self.inExtent(x2, y2)
+ if inExtentP1 and inExtentP2:
+ clipEdges.append((pt1, pt2))
+ elif inExtentP1 and not inExtentP2:
+ pt2 = self.clipLine(x1, y1, equation, leftDir=False)
+ clipEdges.append((pt1, pt2))
+ elif not inExtentP1 and inExtentP2:
+ pt1 = self.clipLine(x2, y2, equation, leftDir=True)
+ clipEdges.append((pt1, pt2))
+ else: # infinite line
+ if edge[1] != -1:
+ x1, y1 = self.vertices[edge[1]][0], self.vertices[edge[1]][1]
+ leftDir = False
+ else:
+ x1, y1 = self.vertices[edge[2]][0], self.vertices[edge[2]][1]
+ leftDir = True
+ if self.inExtent(x1, y1):
+ pt1 = (x1, y1)
+ pt2 = self.clipLine(x1, y1, equation, leftDir)
+ clipEdges.append((pt1, pt2))
+ # create polygon definition from edges and check if polygon is completely closed
+ polyPts, complete = self.orderPts(clipEdges)
+ if not complete:
+ startPt = polyPts[0]
+ endPt = polyPts[-1]
+ # if start & end points are collinear then they are along an extent border
+ if startPt[0] == endPt[0] or startPt[1] == endPt[1]:
+ polyPts.append(polyPts[0]) # simple close
+ else: # close at extent corner
+ # upper left
+ if (startPt[0] == xmin and endPt[1] == ymax) or (endPt[0] == xmin and startPt[1] == ymax):
+ polyPts.append((xmin, ymax)) # corner point
+ polyPts.append(polyPts[0]) # close polygon
+ # upper right
+ if (startPt[0] == xmax and endPt[1] == ymax) or (endPt[0] == xmax and startPt[1] == ymax):
+ polyPts.append((xmax, ymax))
+ polyPts.append(polyPts[0])
+ # bottom right
+ if (startPt[0] == xmax and endPt[1] == ymin) or (endPt[0] == xmax and startPt[1] == ymin):
+ polyPts.append((xmax, ymin))
+ polyPts.append(polyPts[0])
+ # bottom left
+ if (startPt[0] == xmin and endPt[1] == ymin) or (endPt[0] == xmin and startPt[1] == ymin):
+ polyPts.append((xmin, ymin))
+ polyPts.append(polyPts[0])
+ if not closePoly: # unclose polygon
+ polyPts = polyPts[:-1]
+ poly[inPtsIdx] = polyPts
+ return poly
+
+ def clipLine(self, x1, y1, equation, leftDir):
+ xmin, xmax, ymin, ymax = self.extent
+ a, b, c = equation
+ if b == 0: # vertical line
+ if leftDir: # left is bottom of vertical line
+ return (x1, ymax)
+ else:
+ return (x1, ymin)
+ elif a == 0: # horizontal line
+ if leftDir:
+ return (xmin, y1)
+ else:
+ return (xmax, y1)
+ else:
+ y2_at_xmin = (c - a * xmin) / b
+ y2_at_xmax = (c - a * xmax) / b
+ x2_at_ymin = (c - b * ymin) / a
+ x2_at_ymax = (c - b * ymax) / a
+ intersectPts = []
+ if ymin <= y2_at_xmin <= ymax: # valid intersect point
+ intersectPts.append((xmin, y2_at_xmin))
+ if ymin <= y2_at_xmax <= ymax:
+ intersectPts.append((xmax, y2_at_xmax))
+ if xmin <= x2_at_ymin <= xmax:
+ intersectPts.append((x2_at_ymin, ymin))
+ if xmin <= x2_at_ymax <= xmax:
+ intersectPts.append((x2_at_ymax, ymax))
+ # delete duplicate (happens if intersect point is at extent corner)
+ intersectPts = set(intersectPts)
+ # choose target intersect point
+ if leftDir:
+ pt = min(intersectPts) # smaller x value
+ else:
+ pt = max(intersectPts)
+ return pt
+
+ def inExtent(self, x, y):
+ xmin, xmax, ymin, ymax = self.extent
+ return x >= xmin and x <= xmax and y >= ymin and y <= ymax
+
+ def orderPts(self, edges):
+ poly = [] # returned polygon points list [pt1, pt2, pt3, pt4 ....]
+ pts = []
+ # get points list
+ for edge in edges:
+ pts.extend([pt for pt in edge])
+ # try to get start & end point
+ try:
+ startPt, endPt = [pt for pt in pts if pts.count(pt) < 2] # start and end point aren't duplicate
+ except: # all points are duplicate --> polygon is complete --> append some or other edge points
+ complete = True
+ firstIdx = 0
+ poly.append(edges[0][0])
+ poly.append(edges[0][1])
+ else: # incomplete --> append the first edge points
+ complete = False
+ # search first edge
+ for i, edge in enumerate(edges):
+ if startPt in edge: # find
+ firstIdx = i
+ break
+ poly.append(edges[firstIdx][0])
+ poly.append(edges[firstIdx][1])
+ if poly[0] != startPt:
+ poly.reverse()
+ # append next points in list
+ del edges[firstIdx]
+ while edges: # all points will be treated when edges list will be empty
+ currentPt = poly[-1] # last item
+ for i, edge in enumerate(edges):
+ if currentPt == edge[0]:
+ poly.append(edge[1])
+ break
+ elif currentPt == edge[1]:
+ poly.append(edge[0])
+ break
+ del edges[i]
+ return poly, complete
+
+ def setClipBuffer(self, xpourcent, ypourcent):
+ xmin, xmax, ymin, ymax = self.extent
+ witdh = xmax - xmin
+ height = ymax - ymin
+ xmin = xmin - witdh * xpourcent / 100
+ xmax = xmax + witdh * xpourcent / 100
+ ymin = ymin - height * ypourcent / 100
+ ymax = ymax + height * ypourcent / 100
+ self.extent = xmin, xmax, ymin, ymax
+
+ # End clip functions #
+
+ def outSite(self, s):
+ if(self.debug):
+ print("site (%d) at %f %f" % (s.sitenum, s.x, s.y))
+ elif(self.triangulate):
+ pass
+ elif(self.doPrint):
+ print("s %f %f" % (s.x, s.y))
+
+ def outVertex(self, s):
+ self.vertices.append((s.x, s.y))
+ if(self.debug):
+ print("vertex(%d) at %f %f" % (s.sitenum, s.x, s.y))
+ elif(self.triangulate):
+ pass
+ elif(self.doPrint):
+ print("v %f %f" % (s.x, s.y))
+
+ def outTriple(self, s1, s2, s3):
+ self.triangles.append((s1.sitenum, s2.sitenum, s3.sitenum))
+ if(self.debug):
+ print("circle through left=%d right=%d bottom=%d" % (s1.sitenum, s2.sitenum, s3.sitenum))
+ elif(self.triangulate and self.doPrint):
+ print("%d %d %d" % (s1.sitenum, s2.sitenum, s3.sitenum))
+
+ def outBisector(self, edge):
+ self.lines.append((edge.a, edge.b, edge.c))
+ if(self.debug):
+ print("line(%d) %gx+%gy=%g, bisecting %d %d" % (edge.edgenum, edge.a, edge.b,
+ edge.c, edge.reg[0].sitenum,
+ edge.reg[1].sitenum)
+ )
+ elif(self.doPrint):
+ print("l %f %f %f" % (edge.a, edge.b, edge.c))
+
+ def outEdge(self, edge):
+ sitenumL = -1
+ if edge.ep[Edge.LE] is not None:
+ sitenumL = edge.ep[Edge.LE].sitenum
+ sitenumR = -1
+ if edge.ep[Edge.RE] is not None:
+ sitenumR = edge.ep[Edge.RE].sitenum
+
+ # polygons dict add by CF
+ if edge.reg[0].sitenum not in self.polygons:
+ self.polygons[edge.reg[0].sitenum] = []
+ if edge.reg[1].sitenum not in self.polygons:
+ self.polygons[edge.reg[1].sitenum] = []
+ self.polygons[edge.reg[0].sitenum].append((edge.edgenum, sitenumL, sitenumR))
+ self.polygons[edge.reg[1].sitenum].append((edge.edgenum, sitenumL, sitenumR))
+
+ self.edges.append((edge.edgenum, sitenumL, sitenumR))
+
+ if(not self.triangulate):
+ if(self.doPrint):
+ print("e %d" % edge.edgenum)
+ print(" %d " % sitenumL)
+ print("%d" % sitenumR)
+
+
+def voronoi(siteList, context):
+ context.extent = siteList.extent
+ edgeList = EdgeList(siteList.xmin, siteList.xmax, len(siteList))
+ priorityQ = PriorityQueue(siteList.ymin, siteList.ymax, len(siteList))
+ siteIter = siteList.iterator()
+
+ bottomsite = siteIter.next()
+ context.outSite(bottomsite)
+ newsite = siteIter.next()
+ minpt = Site(-BIG_FLOAT, -BIG_FLOAT)
+ while True:
+ if not priorityQ.isEmpty():
+ minpt = priorityQ.getMinPt()
+
+ if (newsite and (priorityQ.isEmpty() or newsite < minpt)):
+ # newsite is smallest - this is a site event
+ context.outSite(newsite)
+
+ # get first Halfedge to the LEFT and RIGHT of the new site
+ lbnd = edgeList.leftbnd(newsite)
+ rbnd = lbnd.right
+
+ # if this halfedge has no edge, bot = bottom site (whatever that is)
+ # create a new edge that bisects
+ bot = lbnd.rightreg(bottomsite)
+ edge = Edge.bisect(bot, newsite)
+ context.outBisector(edge)
+
+ # create a new Halfedge, setting its pm field to 0 and insert
+ # this new bisector edge between the left and right vectors in
+ # a linked list
+ bisector = Halfedge(edge, Edge.LE)
+ edgeList.insert(lbnd, bisector)
+
+ # if the new bisector intersects with the left edge, remove
+ # the left edge's vertex, and put in the new one
+ p = lbnd.intersect(bisector)
+ if p is not None:
+ priorityQ.delete(lbnd)
+ priorityQ.insert(lbnd, p, newsite.distance(p))
+
+ # create a new Halfedge, setting its pm field to 1
+ # insert the new Halfedge to the right of the original bisector
+ lbnd = bisector
+ bisector = Halfedge(edge, Edge.RE)
+ edgeList.insert(lbnd, bisector)
+
+ # if this new bisector intersects with the right Halfedge
+ p = bisector.intersect(rbnd)
+ if p is not None:
+ # push the Halfedge into the ordered linked list of vertices
+ priorityQ.insert(bisector, p, newsite.distance(p))
+
+ newsite = siteIter.next()
+
+ elif not priorityQ.isEmpty():
+ # intersection is smallest - this is a vector (circle) event
+ # pop the Halfedge with the lowest vector off the ordered list of
+ # vectors. Get the Halfedge to the left and right of the above HE
+ # and also the Halfedge to the right of the right HE
+ lbnd = priorityQ.popMinHalfedge()
+ llbnd = lbnd.left
+ rbnd = lbnd.right
+ rrbnd = rbnd.right
+
+ # get the Site to the left of the left HE and to the right of
+ # the right HE which it bisects
+ bot = lbnd.leftreg(bottomsite)
+ top = rbnd.rightreg(bottomsite)
+
+ # output the triple of sites, stating that a circle goes through them
+ mid = lbnd.rightreg(bottomsite)
+ context.outTriple(bot, top, mid)
+
+ # get the vertex that caused this event and set the vertex number
+ # couldn't do this earlier since we didn't know when it would be processed
+ v = lbnd.vertex
+ siteList.setSiteNumber(v)
+ context.outVertex(v)
+
+ # set the endpoint of the left and right Halfedge to be this vector
+ if lbnd.edge.setEndpoint(lbnd.pm, v):
+ context.outEdge(lbnd.edge)
+
+ if rbnd.edge.setEndpoint(rbnd.pm, v):
+ context.outEdge(rbnd.edge)
+
+ # delete the lowest HE, remove all vertex events to do with the
+ # right HE and delete the right HE
+ edgeList.delete(lbnd)
+ priorityQ.delete(rbnd)
+ edgeList.delete(rbnd)
+
+ # if the site to the left of the event is higher than the Site
+ # to the right of it, then swap them and set 'pm' to RIGHT
+ pm = Edge.LE
+ if bot.y > top.y:
+ bot, top = top, bot
+ pm = Edge.RE
+
+ # Create an Edge (or line) that is between the two Sites. This
+ # creates the formula of the line, and assigns a line number to it
+ edge = Edge.bisect(bot, top)
+ context.outBisector(edge)
+
+ # create a HE from the edge
+ bisector = Halfedge(edge, pm)
+
+ # insert the new bisector to the right of the left HE
+ # set one endpoint to the new edge to be the vector point 'v'
+ # If the site to the left of this bisector is higher than the right
+ # Site, then this endpoint is put in position 0; otherwise in pos 1
+ edgeList.insert(llbnd, bisector)
+ if edge.setEndpoint(Edge.RE - pm, v):
+ context.outEdge(edge)
+
+ # if left HE and the new bisector don't intersect, then delete
+ # the left HE, and reinsert it
+ p = llbnd.intersect(bisector)
+ if p is not None:
+ priorityQ.delete(llbnd)
+ priorityQ.insert(llbnd, p, bot.distance(p))
+
+ # if right HE and the new bisector don't intersect, then reinsert it
+ p = bisector.intersect(rrbnd)
+ if p is not None:
+ priorityQ.insert(bisector, p, bot.distance(p))
+ else:
+ break
+
+ he = edgeList.leftend.right
+ while he is not edgeList.rightend:
+ context.outEdge(he.edge)
+ he = he.right
+ Edge.EDGE_NUM = 0 # CF
+
+
+def isEqual(a, b, relativeError=TOLERANCE):
+ # is nearly equal to within the allowed relative error
+ norm = max(abs(a), abs(b))
+ return (norm < relativeError) or (abs(a - b) < (relativeError * norm))
+
+
+class Site(object):
+
+ def __init__(self, x=0.0, y=0.0, sitenum=0):
+ self.x = x
+ self.y = y
+ self.sitenum = sitenum
+
+ def dump(self):
+ print("Site #%d (%g, %g)" % (self.sitenum, self.x, self.y))
+
+ def __lt__(self, other):
+ if self.y < other.y:
+ return True
+ elif self.y > other.y:
+ return False
+ elif self.x < other.x:
+ return True
+ elif self.x > other.x:
+ return False
+ else:
+ return False
+
+ def __eq__(self, other):
+ if self.y == other.y and self.x == other.x:
+ return True
+
+ def distance(self, other):
+ dx = self.x - other.x
+ dy = self.y - other.y
+ return math.sqrt(dx * dx + dy * dy)
+
+
+class Edge(object):
+ LE = 0 # left end indice --> edge.ep[Edge.LE]
+ RE = 1 # right end indice
+ EDGE_NUM = 0
+ DELETED = {} # marker value
+
+ def __init__(self):
+ self.a = 0.0 # equation of the line a*x+b*y = c
+ self.b = 0.0
+ self.c = 0.0
+ self.ep = [None, None] # end point (2 tuples of site)
+ self.reg = [None, None]
+ self.edgenum = 0
+
+ def dump(self):
+ print("(#%d a=%g, b=%g, c=%g)" % (self.edgenum, self.a, self.b, self.c))
+ print("ep", self.ep)
+ print("reg", self.reg)
+
+ def setEndpoint(self, lrFlag, site):
+ self.ep[lrFlag] = site
+ if self.ep[Edge.RE - lrFlag] is None:
+ return False
+ return True
+
+ @staticmethod
+ def bisect(s1, s2):
+ newedge = Edge()
+ newedge.reg[0] = s1 # store the sites that this edge is bisecting
+ newedge.reg[1] = s2
+
+ # to begin with, there are no endpoints on the bisector - it goes to infinity
+ # ep[0] and ep[1] are None
+
+ # get the difference in x dist between the sites
+ dx = float(s2.x - s1.x)
+ dy = float(s2.y - s1.y)
+ adx = abs(dx) # make sure that the difference in positive
+ ady = abs(dy)
+
+ # get the slope of the line
+ newedge.c = float(s1.x * dx + s1.y * dy + (dx * dx + dy * dy) * 0.5)
+ if adx > ady:
+ # set formula of line, with x fixed to 1
+ newedge.a = 1.0
+ newedge.b = dy / dx
+ newedge.c /= dx
+ else:
+ # set formula of line, with y fixed to 1
+ newedge.b = 1.0
+ newedge.a = dx / dy
+ newedge.c /= dy
+
+ newedge.edgenum = Edge.EDGE_NUM
+ Edge.EDGE_NUM += 1
+ return newedge
+
+
+class Halfedge(object):
+
+ def __init__(self, edge=None, pm=Edge.LE):
+ self.left = None # left Halfedge in the edge list
+ self.right = None # right Halfedge in the edge list
+ self.qnext = None # priority queue linked list pointer
+ self.edge = edge # edge list Edge
+ self.pm = pm
+ self.vertex = None # Site()
+ self.ystar = BIG_FLOAT
+
+ def dump(self):
+ print("Halfedge--------------------------")
+ print("left: ", self.left)
+ print("right: ", self.right)
+ print("edge: ", self.edge)
+ print("pm: ", self.pm)
+ print("vertex: "),
+ if self.vertex:
+ self.vertex.dump()
+ else:
+ print("None")
+ print("ystar: ", self.ystar)
+
+ def __lt__(self, other):
+ if self.ystar < other.ystar:
+ return True
+ elif self.ystar > other.ystar:
+ return False
+ elif self.vertex.x < other.vertex.x:
+ return True
+ elif self.vertex.x > other.vertex.x:
+ return False
+ else:
+ return False
+
+ def __eq__(self, other):
+ if self.ystar == other.ystar and self.vertex.x == other.vertex.x:
+ return True
+
+ def leftreg(self, default):
+ if not self.edge:
+ return default
+ elif self.pm == Edge.LE:
+ return self.edge.reg[Edge.LE]
+ else:
+ return self.edge.reg[Edge.RE]
+
+ def rightreg(self, default):
+ if not self.edge:
+ return default
+ elif self.pm == Edge.LE:
+ return self.edge.reg[Edge.RE]
+ else:
+ return self.edge.reg[Edge.LE]
+
+ # returns True if p is to right of halfedge self
+ def isPointRightOf(self, pt):
+ e = self.edge
+ topsite = e.reg[1]
+ right_of_site = pt.x > topsite.x
+
+ if(right_of_site and self.pm == Edge.LE):
+ return True
+
+ if(not right_of_site and self.pm == Edge.RE):
+ return False
+
+ if(e.a == 1.0):
+ dyp = pt.y - topsite.y
+ dxp = pt.x - topsite.x
+ fast = 0
+ if ((not right_of_site and e.b < 0.0) or (right_of_site and e.b >= 0.0)):
+ above = dyp >= e.b * dxp
+ fast = above
+ else:
+ above = pt.x + pt.y * e.b > e.c
+ if(e.b < 0.0):
+ above = not above
+ if (not above):
+ fast = 1
+ if (not fast):
+ dxs = topsite.x - (e.reg[0]).x
+ above = e.b * (dxp * dxp - dyp * dyp) < dxs * dyp * (1.0 + 2.0 * dxp / dxs + e.b * e.b)
+ if(e.b < 0.0):
+ above = not above
+ else: # e.b == 1.0
+ yl = e.c - e.a * pt.x
+ t1 = pt.y - yl
+ t2 = pt.x - topsite.x
+ t3 = yl - topsite.y
+ above = t1 * t1 > t2 * t2 + t3 * t3
+
+ if(self.pm == Edge.LE):
+ return above
+ else:
+ return not above
+
+ # create a new site where the Halfedges el1 and el2 intersect
+ def intersect(self, other):
+ e1 = self.edge
+ e2 = other.edge
+ if (e1 is None) or (e2 is None):
+ return None
+
+ # if the two edges bisect the same parent return None
+ if e1.reg[1] is e2.reg[1]:
+ return None
+
+ d = e1.a * e2.b - e1.b * e2.a
+ if isEqual(d, 0.0):
+ return None
+
+ xint = (e1.c * e2.b - e2.c * e1.b) / d
+ yint = (e2.c * e1.a - e1.c * e2.a) / d
+ if e1.reg[1] < e2.reg[1]:
+ he = self
+ e = e1
+ else:
+ he = other
+ e = e2
+
+ rightOfSite = xint >= e.reg[1].x
+ if((rightOfSite and he.pm == Edge.LE) or
+ (not rightOfSite and he.pm == Edge.RE)):
+ return None
+
+ # create a new site at the point of intersection - this is a new
+ # vector event waiting to happen
+ return Site(xint, yint)
+
+
+class EdgeList(object):
+
+ def __init__(self, xmin, xmax, nsites):
+ if xmin > xmax:
+ xmin, xmax = xmax, xmin
+ self.hashsize = int(2 * math.sqrt(nsites + 4))
+
+ self.xmin = xmin
+ self.deltax = float(xmax - xmin)
+ self.hash = [None] * self.hashsize
+
+ self.leftend = Halfedge()
+ self.rightend = Halfedge()
+ self.leftend.right = self.rightend
+ self.rightend.left = self.leftend
+ self.hash[0] = self.leftend
+ self.hash[-1] = self.rightend
+
+ def insert(self, left, he):
+ he.left = left
+ he.right = left.right
+ left.right.left = he
+ left.right = he
+
+ def delete(self, he):
+ he.left.right = he.right
+ he.right.left = he.left
+ he.edge = Edge.DELETED
+
+ # Get entry from hash table, pruning any deleted nodes
+ def gethash(self, b):
+ if(b < 0 or b >= self.hashsize):
+ return None
+ he = self.hash[b]
+ if he is None or he.edge is not Edge.DELETED:
+ return he
+
+ # Hash table points to deleted half edge. Patch as necessary.
+ self.hash[b] = None
+ return None
+
+ def leftbnd(self, pt):
+ # Use hash table to get close to desired halfedge
+ bucket = int(((pt.x - self.xmin) / self.deltax * self.hashsize))
+
+ if(bucket < 0):
+ bucket = 0
+
+ if(bucket >= self.hashsize):
+ bucket = self.hashsize - 1
+
+ he = self.gethash(bucket)
+ if(he is None):
+ i = 1
+ while True:
+ he = self.gethash(bucket - i)
+ if (he is not None):
+ break
+ he = self.gethash(bucket + i)
+ if (he is not None):
+ break
+ i += 1
+
+ # Now search linear list of halfedges for the corect one
+ if (he is self.leftend) or (he is not self.rightend and he.isPointRightOf(pt)):
+ he = he.right
+ while he is not self.rightend and he.isPointRightOf(pt):
+ he = he.right
+ he = he.left
+ else:
+ he = he.left
+ while (he is not self.leftend and not he.isPointRightOf(pt)):
+ he = he.left
+
+ # Update hash table and reference counts
+ if(bucket > 0 and bucket < self.hashsize - 1):
+ self.hash[bucket] = he
+ return he
+
+
+class PriorityQueue(object):
+
+ def __init__(self, ymin, ymax, nsites):
+ self.ymin = ymin
+ self.deltay = ymax - ymin
+ self.hashsize = int(4 * math.sqrt(nsites))
+ self.count = 0
+ self.minidx = 0
+ self.hash = []
+ for i in range(self.hashsize):
+ self.hash.append(Halfedge())
+
+ def __len__(self):
+ return self.count
+
+ def isEmpty(self):
+ return self.count == 0
+
+ def insert(self, he, site, offset):
+ he.vertex = site
+ he.ystar = site.y + offset
+ last = self.hash[self.getBucket(he)]
+ next = last.qnext
+ while((next is not None) and he > next):
+ last = next
+ next = last.qnext
+ he.qnext = last.qnext
+ last.qnext = he
+ self.count += 1
+
+ def delete(self, he):
+ if (he.vertex is not None):
+ last = self.hash[self.getBucket(he)]
+ while last.qnext is not he:
+ last = last.qnext
+ last.qnext = he.qnext
+ self.count -= 1
+ he.vertex = None
+
+ def getBucket(self, he):
+ bucket = int(((he.ystar - self.ymin) / self.deltay) * self.hashsize)
+ if bucket < 0:
+ bucket = 0
+ if bucket >= self.hashsize:
+ bucket = self.hashsize - 1
+ if bucket < self.minidx:
+ self.minidx = bucket
+ return bucket
+
+ def getMinPt(self):
+ while(self.hash[self.minidx].qnext is None):
+ self.minidx += 1
+ he = self.hash[self.minidx].qnext
+ x = he.vertex.x
+ y = he.ystar
+ return Site(x, y)
+
+ def popMinHalfedge(self):
+ curr = self.hash[self.minidx].qnext
+ self.hash[self.minidx].qnext = curr.qnext
+ self.count -= 1
+ return curr
+
+
+class SiteList(object):
+
+ def __init__(self, pointList):
+ self.__sites = []
+ self.__sitenum = 0
+
+ self.__xmin = min([pt.x for pt in pointList])
+ self.__ymin = min([pt.y for pt in pointList])
+ self.__xmax = max([pt.x for pt in pointList])
+ self.__ymax = max([pt.y for pt in pointList])
+ self.__extent = (self.__xmin, self.__xmax, self.__ymin, self.__ymax)
+
+ for i, pt in enumerate(pointList):
+ self.__sites.append(Site(pt.x, pt.y, i))
+ self.__sites.sort()
+
+ def setSiteNumber(self, site):
+ site.sitenum = self.__sitenum
+ self.__sitenum += 1
+
+ class Iterator(object):
+
+ def __init__(this, lst):
+ this.generator = (s for s in lst)
+
+ def __iter__(this):
+ return this
+
+ def next(this):
+ try:
+ # Note: Blender is Python 3.x so no need for 2.x checks
+ return this.generator.__next__()
+ except StopIteration:
+ return None
+
+ def iterator(self):
+ return SiteList.Iterator(self.__sites)
+
+ def __iter__(self):
+ return SiteList.Iterator(self.__sites)
+
+ def __len__(self):
+ return len(self.__sites)
+
+ def _getxmin(self):
+ return self.__xmin
+
+ def _getymin(self):
+ return self.__ymin
+
+ def _getxmax(self):
+ return self.__xmax
+
+ def _getymax(self):
+ return self.__ymax
+
+ def _getextent(self):
+ return self.__extent
+
+ xmin = property(_getxmin)
+ ymin = property(_getymin)
+ xmax = property(_getxmax)
+ ymax = property(_getymax)
+ extent = property(_getextent)
+
+
+def computeVoronoiDiagram(points, xBuff=0, yBuff=0, polygonsOutput=False,
+ formatOutput=False, closePoly=True):
+ """
+ Takes :
+ - a list of point objects (which must have x and y fields).
+ - x and y buffer values which are the expansion percentages of the bounding box
+ rectangle including all input points.
+ Returns :
+ - With default options :
+ A list of 2-tuples, representing the two points of each Voronoi diagram edge.
+ Each point contains 2-tuples which are the x,y coordinates of point.
+ if formatOutput is True, returns :
+ - a list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
+ - and a list of 2-tuples (v1, v2) representing edges of the Voronoi diagram.
+ v1 and v2 are the indices of the vertices at the end of the edge.
+ - If polygonsOutput option is True, returns :
+ A dictionary of polygons, keys are the indices of the input points,
+ values contains n-tuples representing the n points of each Voronoi diagram polygon.
+ Each point contains 2-tuples which are the x,y coordinates of point.
+ if formatOutput is True, returns :
+ - A list of 2-tuples, which are the x,y coordinates of the Voronoi diagram vertices.
+ - and a dictionary of input points indices. Values contains n-tuples representing
+ the n points of each Voronoi diagram polygon.
+ Each tuple contains the vertex indices of the polygon vertices.
+ - if closePoly is True then, in the list of points of a polygon, last point will be the same of first point
+ """
+ siteList = SiteList(points)
+ context = Context()
+ voronoi(siteList, context)
+ context.setClipBuffer(xBuff, yBuff)
+ if not polygonsOutput:
+ clipEdges = context.getClipEdges()
+ if formatOutput:
+ vertices, edgesIdx = formatEdgesOutput(clipEdges)
+ return vertices, edgesIdx
+ else:
+ return clipEdges
+ else:
+ clipPolygons = context.getClipPolygons(closePoly)
+ if formatOutput:
+ vertices, polyIdx = formatPolygonsOutput(clipPolygons)
+ return vertices, polyIdx
+ else:
+ return clipPolygons
+
+
+def formatEdgesOutput(edges):
+ # get list of points
+ pts = []
+ for edge in edges:
+ pts.extend(edge)
+ # get unique values
+ pts = set(pts) # unique values (tuples are hashable)
+ # get dict {values:index}
+ valuesIdxDict = dict(zip(pts, range(len(pts))))
+ # get edges index reference
+ edgesIdx = []
+ for edge in edges:
+ edgesIdx.append([valuesIdxDict[pt] for pt in edge])
+ return list(pts), edgesIdx
+
+
+def formatPolygonsOutput(polygons):
+ # get list of points
+ pts = []
+ for poly in polygons.values():
+ pts.extend(poly)
+ # get unique values
+ pts = set(pts) # unique values (tuples are hashable)
+ # get dict {values:index}
+ valuesIdxDict = dict(zip(pts, range(len(pts))))
+ # get polygons index reference
+ polygonsIdx = {}
+ for inPtsIdx, poly in polygons.items():
+ polygonsIdx[inPtsIdx] = [valuesIdxDict[pt] for pt in poly]
+ return list(pts), polygonsIdx
+
+
+def computeDelaunayTriangulation(points):
+ """ Takes a list of point objects (which must have x and y fields).
+ Returns a list of 3-tuples: the indices of the points that form a
+ Delaunay triangle.
+ """
+ siteList = SiteList(points)
+ context = Context()
+ context.triangulate = True
+ voronoi(siteList, context)
+ return context.triangles
diff --git a/add_advanced_objects/delaunay_voronoi/__init__.py b/add_advanced_objects/delaunay_voronoi/__init__.py
new file mode 100644
index 00000000..1d210a2a
--- /dev/null
+++ b/add_advanced_objects/delaunay_voronoi/__init__.py
@@ -0,0 +1,52 @@
+# -*- coding:utf-8 -*-
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Delaunay Voronoi",
+ "description": "Points cloud Delaunay triangulation in 2.5D "
+ "(suitable for terrain modelling) or Voronoi diagram in 2D",
+ "author": "Domlysz, Oscurart",
+ "version": (1, 3),
+ "blender": (2, 7, 0),
+ "location": "View3D > Tools > GIS",
+ "warning": "",
+ "wiki_url": "https://github.com/domlysz/BlenderGIS/wiki",
+ "tracker_url": "",
+ "category": ""
+ }
+
+if "bpy" in locals():
+ import importlib
+ importlib.reload(oscurart_constellation)
+
+else:
+ from . import oscurart_constellation
+
+import bpy
+from .delaunayVoronoiBlender import ToolsPanelDelaunay
+
+
+# Register
+def register():
+ bpy.utils.register_module(__name__)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
diff --git a/add_advanced_objects/delaunay_voronoi/delaunayVoronoiBlender.py b/add_advanced_objects/delaunay_voronoi/delaunayVoronoiBlender.py
new file mode 100644
index 00000000..e937e7a1
--- /dev/null
+++ b/add_advanced_objects/delaunay_voronoi/delaunayVoronoiBlender.py
@@ -0,0 +1,234 @@
+# -*- coding:utf-8 -*-
+
+import bpy
+from .DelaunayVoronoi import (
+ computeVoronoiDiagram,
+ computeDelaunayTriangulation,
+ )
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+from bpy.props import EnumProperty
+
+
+class Point:
+ def __init__(self, x, y, z):
+ self.x, self.y, self.z = x, y, z
+
+
+def unique(L):
+ """Return a list of unhashable elements in s, but without duplicates.
+ [[1, 2], [2, 3], [1, 2]] >>> [[1, 2], [2, 3]]"""
+ # For unhashable objects, you can sort the sequence and
+ # then scan from the end of the list, deleting duplicates as you go
+ nDupli = 0
+ nZcolinear = 0
+ # sort() brings the equal elements together; then duplicates
+ # are easy to weed out in a single pass
+ L.sort()
+ last = L[-1]
+ for i in range(len(L) - 2, -1, -1):
+ if last[:2] == L[i][:2]: # XY coordinates compararison
+ if last[2] == L[i][2]: # Z coordinates compararison
+ nDupli += 1 # duplicates vertices
+ else: # Z colinear
+ nZcolinear += 1
+ del L[i]
+ else:
+ last = L[i]
+ # list data type is mutable, input list will automatically update
+ # and doesn't need to be returned
+ return (nDupli, nZcolinear)
+
+
+def checkEqual(lst):
+ return lst[1:] == lst[:-1]
+
+
+class ToolsPanelDelaunay(Panel):
+ bl_category = "Create"
+ bl_label = "Delaunay Voronoi"
+ bl_space_type = "VIEW_3D"
+ bl_context = "objectmode"
+ bl_region_type = "TOOLS"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ layout = self.layout
+ layout.label('Constellation')
+ self.layout.operator("delaunay.triangulation")
+ self.layout.operator("voronoi.tesselation")
+ layout.label('Constellation')
+ layout.operator("mesh.constellation", text="Cross Section")
+
+
+class OBJECT_OT_TriangulateButton(Operator):
+ bl_idname = "delaunay.triangulation"
+ bl_label = "Triangulation"
+ bl_description = "Terrain points cloud Delaunay triangulation in 2.5D"
+ bl_options = {"UNDO"}
+
+ def execute(self, context):
+ # Get selected obj
+ objs = bpy.context.selected_objects
+ if len(objs) == 0 or len(objs) > 1:
+ self.report({'INFO'}, "Selection is empty or too much object selected")
+ return {'FINISHED'}
+
+ obj = objs[0]
+ if obj.type != 'MESH':
+ self.report({'INFO'}, "Selection isn't a mesh")
+ return {'FINISHED'}
+
+ # Get points coodinates
+ r = obj.rotation_euler
+ s = obj.scale
+ mesh = obj.data
+ vertsPts = [vertex.co for vertex in mesh.vertices]
+ # Remove duplicate
+ verts = [[vert.x, vert.y, vert.z] for vert in vertsPts]
+ nDupli, nZcolinear = unique(verts)
+ nVerts = len(verts)
+ print(str(nDupli) + " duplicates points ignored")
+ print(str(nZcolinear) + " z colinear points excluded")
+ if nVerts < 3:
+ self.report({'ERROR'}, "Not enough points")
+ return {'FINISHED'}
+
+ # Check colinear
+ xValues = [pt[0] for pt in verts]
+ yValues = [pt[1] for pt in verts]
+
+ if checkEqual(xValues) or checkEqual(yValues):
+ self.report({'ERROR'}, "Points are colinear")
+ return {'FINISHED'}
+
+ # Triangulate
+ print("Triangulate " + str(nVerts) + " points...")
+ vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts]
+ triangles = computeDelaunayTriangulation(vertsPts)
+ # reverse point order --> if all triangles are specified anticlockwise then all faces up
+ triangles = [tuple(reversed(tri)) for tri in triangles]
+
+ print(str(len(triangles)) + " triangles")
+
+ # Create new mesh structure
+ print("Create mesh...")
+ tinMesh = bpy.data.meshes.new("TIN") # create a new mesh
+ tinMesh.from_pydata(verts, [], triangles) # Fill the mesh with triangles
+ tinMesh.update(calc_edges=True) # Update mesh with new data
+
+ # Create an object with that mesh
+ tinObj = bpy.data.objects.new("TIN", tinMesh)
+ # Place object
+ tinObj.location = obj.location.copy()
+ tinObj.rotation_euler = r
+ tinObj.scale = s
+ # Update scene
+ bpy.context.scene.objects.link(tinObj) # Link object to scene
+ bpy.context.scene.objects.active = tinObj
+ tinObj.select = True
+ obj.select = False
+ # Report
+ self.report({'INFO'}, "Mesh created (" + str(len(triangles)) + " triangles)")
+ return {'FINISHED'}
+
+
+class OBJECT_OT_VoronoiButton(Operator):
+ bl_idname = "voronoi.tesselation"
+ bl_label = "Diagram"
+ bl_description = "Points cloud Voronoi diagram in 2D"
+ bl_options = {"REGISTER", "UNDO"}
+
+ meshType = EnumProperty(
+ items=[("Edges", "Edges", ""), ("Faces", "Faces", "")],
+ name="Mesh type",
+ description=""
+ )
+
+ def execute(self, context):
+ # Get selected obj
+ objs = bpy.context.selected_objects
+ if len(objs) == 0 or len(objs) > 1:
+ self.report({'INFO'}, "Selection is empty or too much object selected")
+ return {'FINISHED'}
+
+ obj = objs[0]
+ if obj.type != 'MESH':
+ self.report({'INFO'}, "Selection isn't a mesh")
+ return {'FINISHED'}
+
+ # Get points coodinates
+ r = obj.rotation_euler
+ s = obj.scale
+ mesh = obj.data
+ vertsPts = [vertex.co for vertex in mesh.vertices]
+
+ # Remove duplicate
+ verts = [[vert.x, vert.y, vert.z] for vert in vertsPts]
+ nDupli, nZcolinear = unique(verts)
+ nVerts = len(verts)
+
+ print(str(nDupli) + " duplicates points ignored")
+ print(str(nZcolinear) + " z colinear points excluded")
+
+ if nVerts < 3:
+ self.report({'ERROR'}, "Not enough points")
+ return {'FINISHED'}
+
+ # Check colinear
+ xValues = [pt[0] for pt in verts]
+ yValues = [pt[1] for pt in verts]
+ if checkEqual(xValues) or checkEqual(yValues):
+ self.report({'ERROR'}, "Points are colinear")
+ return {'FINISHED'}
+
+ # Create diagram
+ print("Tesselation... (" + str(nVerts) + " points)")
+ xbuff, ybuff = 5, 5
+ zPosition = 0
+ vertsPts = [Point(vert[0], vert[1], vert[2]) for vert in verts]
+ if self.meshType == "Edges":
+ pts, edgesIdx = computeVoronoiDiagram(
+ vertsPts, xbuff, ybuff, polygonsOutput=False, formatOutput=True
+ )
+ else:
+ pts, polyIdx = computeVoronoiDiagram(
+ vertsPts, xbuff, ybuff, polygonsOutput=True,
+ formatOutput=True, closePoly=False
+ )
+
+ pts = [[pt[0], pt[1], zPosition] for pt in pts]
+
+ # Create new mesh structure
+ voronoiDiagram = bpy.data.meshes.new("VoronoiDiagram") # create a new mesh
+
+ if self.meshType == "Edges":
+ # Fill the mesh with triangles
+ voronoiDiagram.from_pydata(pts, edgesIdx, [])
+ else:
+ # Fill the mesh with triangles
+ voronoiDiagram.from_pydata(pts, [], list(polyIdx.values()))
+
+ voronoiDiagram.update(calc_edges=True) # Update mesh with new data
+ # create an object with that mesh
+ voronoiObj = bpy.data.objects.new("VoronoiDiagram", voronoiDiagram)
+ # place object
+ voronoiObj.location = obj.location.copy()
+ voronoiObj.rotation_euler = r
+ voronoiObj.scale = s
+
+ # update scene
+ bpy.context.scene.objects.link(voronoiObj) # Link object to scene
+ bpy.context.scene.objects.active = voronoiObj
+ voronoiObj.select = True
+ obj.select = False
+
+ # Report
+ if self.meshType == "Edges":
+ self.report({'INFO'}, "Mesh created (" + str(len(edgesIdx)) + " edges)")
+ else:
+ self.report({'INFO'}, "Mesh created (" + str(len(polyIdx)) + " polygons)")
+
+ return {'FINISHED'}
diff --git a/add_advanced_objects/delaunay_voronoi/oscurart_constellation.py b/add_advanced_objects/delaunay_voronoi/oscurart_constellation.py
new file mode 100644
index 00000000..babbfdc5
--- /dev/null
+++ b/add_advanced_objects/delaunay_voronoi/oscurart_constellation.py
@@ -0,0 +1,106 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Mesh: Constellation",
+ "author": "Oscurart",
+ "version": (1, 0),
+ "blender": (2, 67, 0),
+ "location": "Add > Mesh > Constellation",
+ "description": "Adds a new Mesh From Selected",
+ "warning": "",
+ "wiki_url": "",
+ "tracker_url": "",
+ "category": "Add Mesh"}
+
+import bpy
+from bpy.types import Operator
+from bpy.props import FloatProperty
+from math import sqrt
+
+
+def VertDis(a, b):
+ dst = sqrt(pow(a.co.x - b.co.x, 2) +
+ pow(a.co.y - b.co.y, 2) +
+ pow(a.co.z - b.co.z, 2))
+ return(dst)
+
+
+def OscConstellation(limit):
+ actobj = bpy.context.object
+ vertlist = []
+ edgelist = []
+ edgei = 0
+
+ for ind, verta in enumerate(actobj.data.vertices[:]):
+ for vertb in actobj.data.vertices[ind:]:
+ if VertDis(verta, vertb) <= limit:
+ vertlist.append(verta.co[:])
+ vertlist.append(vertb.co[:])
+ edgelist.append((edgei, edgei + 1))
+ edgei += 2
+
+ mesh = bpy.data.meshes.new("rsdata")
+ object = bpy.data.objects.new("rsObject", mesh)
+ bpy.context.scene.objects.link(object)
+ mesh.from_pydata(vertlist, edgelist, [])
+
+
+class Oscurart_Constellation (Operator):
+ bl_idname = "mesh.constellation"
+ bl_label = "Constellation"
+ bl_description = "Create a Constellation Mesh"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ limit = FloatProperty(
+ name='Limit',
+ default=2,
+ min=0
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return(bpy.context.active_object.type == "MESH")
+
+ def execute(self, context):
+ OscConstellation(self.limit)
+
+ return {'FINISHED'}
+
+
+# Register
+
+def add_osc_constellation_button(self, context):
+ self.layout.operator(
+ Oscurart_Constellation.bl_idname,
+ text="Constellation",
+ icon="PLUGIN")
+
+
+def register():
+ bpy.utils.register_class(Oscurart_Constellation)
+ bpy.types.INFO_MT_mesh_add.append(add_osc_constellation_button)
+
+
+def unregister():
+ bpy.utils.unregister_class(Oscurart_Constellation)
+ bpy.types.INFO_MT_mesh_add.remove(add_osc_constellation_button)
+
+
+if __name__ == '__main__':
+ register()
diff --git a/add_advanced_objects/drop_to_ground.py b/add_advanced_objects/drop_to_ground.py
new file mode 100644
index 00000000..801b7e94
--- /dev/null
+++ b/add_advanced_objects/drop_to_ground.py
@@ -0,0 +1,315 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Drop to Ground1",
+ "author": "Unnikrishnan(kodemax), Florian Meyer(testscreenings)",
+ "version": (1, 2),
+ "blender": (2, 71, 0),
+ "location": "3D View > Toolshelf > Tools Tab",
+ "description": "Drop selected objects on active object",
+ "warning": "",
+ "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
+ "Scripts/Object/Drop_to_ground",
+ "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
+ "category": "Object"}
+
+
+import bpy
+import bmesh
+from mathutils import (
+ Vector,
+ Matrix,
+ )
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+from bpy.props import BoolProperty
+
+
+def get_align_matrix(location, normal):
+ up = Vector((0, 0, 1))
+ angle = normal.angle(up)
+ axis = up.cross(normal)
+ mat_rot = Matrix.Rotation(angle, 4, axis)
+ mat_loc = Matrix.Translation(location)
+ mat_align = mat_rot * mat_loc
+ return mat_align
+
+
+def transform_ground_to_world(sc, ground):
+ tmpMesh = ground.to_mesh(sc, True, 'PREVIEW')
+ tmpMesh.transform(ground.matrix_world)
+ tmp_ground = bpy.data.objects.new('tmpGround', tmpMesh)
+ sc.objects.link(tmp_ground)
+ sc.update()
+ return tmp_ground
+
+
+def get_lowest_world_co_from_mesh(ob, mat_parent=None):
+ bme = bmesh.new()
+ bme.from_mesh(ob.data)
+ mat_to_world = ob.matrix_world.copy()
+ if mat_parent:
+ mat_to_world = mat_parent * mat_to_world
+ lowest = None
+ for v in bme.verts:
+ if not lowest:
+ lowest = v
+ if (mat_to_world * v.co).z < (mat_to_world * lowest.co).z:
+ lowest = v
+ lowest_co = mat_to_world * lowest.co
+ bme.free()
+
+ return lowest_co
+
+
+def get_lowest_world_co(context, ob, mat_parent=None):
+ if ob.type == 'MESH':
+ return get_lowest_world_co_from_mesh(ob)
+
+ elif ob.type == 'EMPTY' and ob.dupli_type == 'GROUP':
+ if not ob.dupli_group:
+ return None
+
+ else:
+ lowest_co = None
+ for ob_l in ob.dupli_group.objects:
+ if ob_l.type == 'MESH':
+ lowest_ob_l = get_lowest_world_co_from_mesh(ob_l, ob.matrix_world)
+ if not lowest_co:
+ lowest_co = lowest_ob_l
+ if lowest_ob_l.z < lowest_co.z:
+ lowest_co = lowest_ob_l
+
+ return lowest_co
+
+
+def drop_objectsall(self, context):
+ ground = bpy.context.active_object
+ name = ground.name
+
+ for obs in bpy.context.scene.objects:
+ obs.select = True
+ if obs.name == name:
+ obs.select = False
+
+ obs2 = context.selected_objects
+
+ tmp_ground = transform_ground_to_world(context.scene, ground)
+ down = Vector((0, 0, -10000))
+
+ for ob in obs2:
+ if self.use_origin:
+ lowest_world_co = ob.location
+ else:
+ lowest_world_co = get_lowest_world_co(context, ob)
+ if not lowest_world_co:
+ print(ob.type, 'is not supported. Failed to drop', ob.name)
+ continue
+ is_hit, hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co, down)
+ if not is_hit:
+ print(ob.name, 'didn\'t hit the ground')
+ continue
+
+ # simple drop down
+ to_ground_vec = hit_location - lowest_world_co
+ ob.location += to_ground_vec
+
+ # drop with align to hit normal
+ if self.align:
+ to_center_vec = ob.location - hit_location # vec: hit_loc to origin
+ # rotate object to align with face normal
+ mat_normal = get_align_matrix(hit_location, hit_normal)
+ rot_euler = mat_normal.to_euler()
+ mat_ob_tmp = ob.matrix_world.copy().to_3x3()
+ mat_ob_tmp.rotate(rot_euler)
+ mat_ob_tmp = mat_ob_tmp.to_4x4()
+ ob.matrix_world = mat_ob_tmp
+ # move_object to hit_location
+ ob.location = hit_location
+ # move object above surface again
+ to_center_vec.rotate(rot_euler)
+ ob.location += to_center_vec
+
+ # cleanup
+ bpy.ops.object.select_all(action='DESELECT')
+ tmp_ground.select = True
+ bpy.ops.object.delete('EXEC_DEFAULT')
+ for ob in obs2:
+ ob.select = True
+ ground.select = True
+
+
+def drop_objects(self, context):
+ ground = context.object
+ obs = context.selected_objects
+ obs.remove(ground)
+ tmp_ground = transform_ground_to_world(context.scene, ground)
+ down = Vector((0, 0, -10000))
+
+ for ob in obs:
+ if self.use_origin:
+ lowest_world_co = ob.location
+ else:
+ lowest_world_co = get_lowest_world_co(context, ob)
+ if not lowest_world_co:
+ print(ob.type, 'is not supported. Failed to drop', ob.name)
+ continue
+ is_hit, hit_location, hit_normal, hit_index = tmp_ground.ray_cast(lowest_world_co, down)
+ if not is_hit:
+ print(ob.name, 'didn\'t hit the ground')
+ continue
+
+ # simple drop down
+ to_ground_vec = hit_location - lowest_world_co
+ ob.location += to_ground_vec
+
+ # drop with align to hit normal
+ if self.align:
+ to_center_vec = ob.location - hit_location # vec: hit_loc to origin
+ # rotate object to align with face normal
+ mat_normal = get_align_matrix(hit_location, hit_normal)
+ rot_euler = mat_normal.to_euler()
+ mat_ob_tmp = ob.matrix_world.copy().to_3x3()
+ mat_ob_tmp.rotate(rot_euler)
+ mat_ob_tmp = mat_ob_tmp.to_4x4()
+ ob.matrix_world = mat_ob_tmp
+ # move_object to hit_location
+ ob.location = hit_location
+ # move object above surface again
+ to_center_vec.rotate(rot_euler)
+ ob.location += to_center_vec
+
+ # cleanup
+ bpy.ops.object.select_all(action='DESELECT')
+ tmp_ground.select = True
+ bpy.ops.object.delete('EXEC_DEFAULT')
+ for ob in obs:
+ ob.select = True
+ ground.select = True
+
+
+class OBJECT_OT_drop_to_ground(Operator):
+ bl_idname = "object.drop_on_active"
+ bl_label = "Drop to Ground"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = "Drop selected objects on active object"
+
+ align = BoolProperty(
+ name="Align to ground",
+ description="Aligns the object to the ground",
+ default=True)
+ use_origin = BoolProperty(
+ name="Use Center",
+ description="Drop to objects origins",
+ default=False)
+
+ @classmethod
+ def poll(cls, context):
+ return len(context.selected_objects) >= 2
+
+ def execute(self, context):
+ print('\nDropping Objects')
+ drop_objects(self, context)
+ return {'FINISHED'}
+
+
+class OBJECT_OT_drop_all_ground(Operator):
+ bl_idname = "object.drop_all_active"
+ bl_label = "Drop to Ground"
+ bl_options = {'REGISTER', 'UNDO'}
+ bl_description = "Drop selected objects on active object"
+
+ align = BoolProperty(
+ name="Align to ground",
+ description="Aligns the object to the ground",
+ default=True)
+ use_origin = BoolProperty(
+ name="Use Center",
+ description="Drop to objects origins",
+ default=False)
+
+ def execute(self, context):
+ print('\nDropping Objects')
+ drop_objectsall(self, context)
+
+ return {'FINISHED'}
+
+
+class drop_help(Operator):
+ bl_idname = "help.drop"
+ bl_label = ""
+
+ def draw(self, context):
+ layout = self.layout
+ layout.label("To use:")
+ layout.label("___________________________")
+
+ layout.label("Drop selected :-")
+
+ layout.label("Name the base object 'Ground'")
+ layout.label("Select the object/s to drop")
+ layout.label("Then Shift Select 'Ground'")
+ layout.label("___________________________")
+
+ layout.label("Drop all :-")
+
+ layout.label("select the ground mesh , and press Drop all")
+
+ def execute(self, context):
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_popup(self, width=300)
+
+
+class Drop_Operator_Panel(Panel):
+ bl_label = "Drop To Ground"
+ bl_region_type = "TOOLS"
+ bl_space_type = "VIEW_3D"
+ bl_options = {'DEFAULT_CLOSED'}
+ bl_context = "objectmode"
+ bl_category = "Create"
+
+ def draw(self, context):
+ layout = self.layout
+ row = layout.row()
+ row = layout.split(0.80)
+ row.operator(OBJECT_OT_drop_to_ground.bl_idname,
+ text="Drop Selected")
+ row = layout.row()
+ row.operator(OBJECT_OT_drop_all_ground.bl_idname,
+ text="Drop All")
+ row.operator('help.drop', icon='INFO')
+
+
+# Register
+def register():
+ bpy.utils.register_module(__name__)
+ pass
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+ pass
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/make_struts.py b/add_advanced_objects/make_struts.py
new file mode 100644
index 00000000..7f754ea8
--- /dev/null
+++ b/add_advanced_objects/make_struts.py
@@ -0,0 +1,571 @@
+# Copyright (C) 2012 Bill Currie <bill@taniwha.org>
+# Date: 2012/2/20
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+"""
+bl_info = {
+ "name": "Strut Generator",
+ "author": "Bill Currie",
+ "blender": (2, 6, 3),
+ "api": 35622,
+ "location": "View3D > Add > Mesh > Struts",
+ "description": "Add struts meshes based on selected truss meshes",
+ "warning": "can get very high-poly",
+ "wiki_url": "",
+ "tracker_url": "",
+ "category": "Add Mesh"}
+"""
+
+import bpy
+import bmesh
+from bpy.props import (
+ FloatProperty,
+ IntProperty,
+ BoolProperty,
+ )
+from mathutils import (
+ Vector,
+ Matrix,
+ Quaternion,
+ )
+from math import (
+ pi,
+ cos,
+ sin,
+ )
+
+cossin = []
+
+# Initialize the cossin table based on the number of segments.
+#
+# @param n The number of segments into which the circle will be
+# divided.
+# @return None
+
+
+def build_cossin(n):
+ global cossin
+ cossin = []
+ for i in range(n):
+ a = 2 * pi * i / n
+ cossin.append((cos(a), sin(a)))
+
+
+def select_up(axis):
+ if (abs(axis[0] / axis.length) < 1e-5 and abs(axis[1] / axis.length) < 1e-5):
+ up = Vector((-1, 0, 0))
+ else:
+ up = Vector((0, 0, 1))
+ return up
+
+# Make a single strut in non-manifold mode.
+#
+# The strut will be a "cylinder" with @a n sides. The vertices of the
+# cylinder will be @a od / 2 from the center of the cylinder. Optionally,
+# extra loops will be placed (@a od - @a id) / 2 from either end. The
+# strut will be either a simple, open-ended single-surface "cylinder", or a
+# double walled "pipe" with the outer wall vertices @a od / 2 from the center
+# and the inner wall vertices @a id / 2 from the center. The two walls will
+# be joined together at the ends with a face ring such that the entire strut
+# is a manifold object. All faces of the strut will be quads.
+#
+# @param v1 Vertex representing one end of the strut's center-line.
+# @param v2 Vertex representing the other end of the strut's
+# center-line.
+# @param id The diameter of the inner wall of a solid strut. Used for
+# calculating the position of the extra loops irrespective
+# of the solidity of the strut.
+# @param od The diameter of the outer wall of a solid strut, or the
+# diameter of a non-solid strut.
+# @param solid If true, the strut will be made solid such that it has an
+# inner wall (diameter @a id), an outer wall (diameter
+# @a od), and face rings at either end of the strut such
+# the strut is a manifold object. If false, the strut is
+# a simple, open-ended "cylinder".
+# @param loops If true, edge loops will be placed at either end of the
+# strut, (@a od - @a id) / 2 from the end of the strut. The
+# loops make subsurfed solid struts work nicely.
+# @return A tuple containing a list of vertices and a list of faces.
+# The face vertex indices are accurate only for the list of
+# vertices for the created strut.
+
+
+def make_strut(v1, v2, id, od, n, solid, loops):
+ v1 = Vector(v1)
+ v2 = Vector(v2)
+ axis = v2 - v1
+ pos = [(0, od / 2)]
+ if loops:
+ pos += [((od - id) / 2, od / 2),
+ (axis.length - (od - id) / 2, od / 2)]
+ pos += [(axis.length, od / 2)]
+ if solid:
+ pos += [(axis.length, id / 2)]
+ if loops:
+ pos += [(axis.length - (od - id) / 2, id / 2),
+ ((od - id) / 2, id / 2)]
+ pos += [(0, id / 2)]
+ vps = len(pos)
+ fps = vps
+ if not solid:
+ fps -= 1
+ fw = axis.copy()
+ fw.normalize()
+ up = select_up(axis)
+ lf = up.cross(fw)
+ lf.normalize()
+ up = fw.cross(lf)
+ mat = Matrix((fw, lf, up))
+ mat.transpose()
+ verts = [None] * n * vps
+ faces = [None] * n * fps
+ for i in range(n):
+ base = (i - 1) * vps
+ x = cossin[i][0]
+ y = cossin[i][1]
+ for j in range(vps):
+ p = Vector((pos[j][0], pos[j][1] * x, pos[j][1] * y))
+ p = mat * p
+ verts[i * vps + j] = p + v1
+ if i:
+ for j in range(fps):
+ f = (i - 1) * fps + j
+ faces[f] = [base + j, base + vps + j,
+ base + vps + (j + 1) % vps, base + (j + 1) % vps]
+ base = len(verts) - vps
+ i = n
+ for j in range(fps):
+ f = (i - 1) * fps + j
+ faces[f] = [base + j, j, (j + 1) % vps, base + (j + 1) % vps]
+ # print(verts,faces)
+ return verts, faces
+
+
+# Project a point along a vector onto a plane.
+#
+# Really, just find the intersection of the line represented by @a point
+# and @a dir with the plane represented by @a norm and @a p. However, if
+# the point is on or in front of the plane, or the line is parallel to
+# the plane, the original point will be returned.
+#
+# @param point The point to be projected onto the plane.
+# @param dir The vector along which the point will be projected.
+# @param norm The normal of the plane onto which the point will be
+# projected.
+# @param p A point through which the plane passes.
+# @return A vector representing the projected point, or the
+# original point.
+
+def project_point(point, dir, norm, p):
+ d = (point - p).dot(norm)
+ if d >= 0:
+ # the point is already on or in front of the plane
+ return point
+ v = dir.dot(norm)
+ if v * v < 1e-8:
+ # the plane is unreachable
+ return point
+ return point - dir * d / v
+
+
+# Make a simple strut for debugging.
+#
+# The strut is just a single quad representing the Z axis of the edge.
+#
+# @param mesh The base mesh. Used for finding the edge vertices.
+# @param edge_num The number of the current edge. For the face vertex
+# indices.
+# @param edge The edge for which the strut will be built.
+# @param od Twice the width of the strut.
+# @return A tuple containing a list of vertices and a list of faces.
+# The face vertex indices are pre-adjusted by the edge
+# number.
+# @fixme The face vertex indices should be accurate for the local
+# vertices (consistency)
+
+def make_debug_strut(mesh, edge_num, edge, od):
+ v = [mesh.verts[edge.verts[0].index].co,
+ mesh.verts[edge.verts[1].index].co,
+ None, None]
+ v[2] = v[1] + edge.z * od / 2
+ v[3] = v[0] + edge.z * od / 2
+ f = [[edge_num * 4 + 0, edge_num * 4 + 1,
+ edge_num * 4 + 2, edge_num * 4 + 3]]
+ return v, f
+
+
+# Make a cylinder with ends clipped to the end-planes of the edge.
+#
+# The strut is just a single quad representing the Z axis of the edge.
+#
+# @param mesh The base mesh. Used for finding the edge vertices.
+# @param edge_num The number of the current edge. For the face vertex
+# indices.
+# @param edge The edge for which the strut will be built.
+# @param od The diameter of the strut.
+# @return A tuple containing a list of vertices and a list of faces.
+# The face vertex indices are pre-adjusted by the edge
+# number.
+# @fixme The face vertex indices should be accurate for the local
+# vertices (consistency)
+
+def make_clipped_cylinder(mesh, edge_num, edge, od):
+ n = len(cossin)
+ cyl = [None] * n
+ v0 = mesh.verts[edge.verts[0].index].co
+ c0 = v0 + od * edge.y
+ v1 = mesh.verts[edge.verts[1].index].co
+ c1 = v1 - od * edge.y
+ for i in range(n):
+ x = cossin[i][0]
+ y = cossin[i][1]
+ r = (edge.z * x - edge.x * y) * od / 2
+ cyl[i] = [c0 + r, c1 + r]
+ for p in edge.verts[0].planes:
+ cyl[i][0] = project_point(cyl[i][0], edge.y, p, v0)
+ for p in edge.verts[1].planes:
+ cyl[i][1] = project_point(cyl[i][1], -edge.y, p, v1)
+ v = [None] * n * 2
+ f = [None] * n
+ base = edge_num * n * 2
+ for i in range(n):
+ v[i * 2 + 0] = cyl[i][1]
+ v[i * 2 + 1] = cyl[i][0]
+ f[i] = [None] * 4
+ f[i][0] = base + i * 2 + 0
+ f[i][1] = base + i * 2 + 1
+ f[i][2] = base + (i * 2 + 3) % (n * 2)
+ f[i][3] = base + (i * 2 + 2) % (n * 2)
+ return v, f
+
+
+# Represent a vertex in the base mesh, with additional information.
+#
+# These vertices are @b not shared between edges.
+#
+# @var index The index of the vert in the base mesh
+# @var edge The edge to which this vertex is attached.
+# @var edges A tuple of indicess of edges attached to this vert, not
+# including the edge to which this vertex is attached.
+# @var planes List of vectors representing the normals of the planes that
+# bisect the angle between this vert's edge and each other
+# adjacant edge.
+
+class SVert:
+ # Create a vertex holding additional information about the bmesh vertex.
+ # @param bmvert The bmesh vertex for which additional information is
+ # to be stored.
+ # @param bmedge The edge to which this vertex is attached.
+
+ def __init__(self, bmvert, bmedge, edge):
+ self.index = bmvert.index
+ self.edge = edge
+ edges = bmvert.link_edges[:]
+ edges.remove(bmedge)
+ self.edges = tuple(map(lambda e: e.index, edges))
+ self.planes = []
+
+ def calc_planes(self, edges):
+ for ed in self.edges:
+ self.planes.append(calc_plane_normal(self.edge, edges[ed]))
+
+
+# Represent an edge in the base mesh, with additional information.
+#
+# Edges do not share vertices so that the edge is always on the front (back?
+# must verify) side of all the planes attached to its vertices. If the
+# vertices were shared, the edge could be on either side of the planes, and
+# there would be planes attached to the vertex that are irrelevant to the
+# edge.
+#
+# @var index The index of the edge in the base mesh.
+# @var bmedge Cached reference to this edge's bmedge
+# @var verts A tuple of 2 SVert vertices, one for each end of the
+# edge. The vertices are @b not shared between edges.
+# However, if two edges are connected via a vertex in the
+# bmesh, their corresponding SVert vertices will have the
+# the same index value.
+# @var x The x axis of the edges local frame of reference.
+# Initially invalid.
+# @var y The y axis of the edges local frame of reference.
+# Initialized such that the edge runs from verts[0] to
+# verts[1] along the negative y axis.
+# @var z The z axis of the edges local frame of reference.
+# Initially invalid.
+
+
+class SEdge:
+
+ def __init__(self, bmesh, bmedge):
+
+ self.index = bmedge.index
+ self.bmedge = bmedge
+ bmesh.verts.ensure_lookup_table()
+ self.verts = (SVert(bmedge.verts[0], bmedge, self),
+ SVert(bmedge.verts[1], bmedge, self))
+ self.y = (bmesh.verts[self.verts[0].index].co -
+ bmesh.verts[self.verts[1].index].co)
+ self.y.normalize()
+ self.x = self.z = None
+
+ def set_frame(self, up):
+ self.x = self.y.cross(up)
+ self.x.normalize()
+ self.z = self.x.cross(self.y)
+
+ def calc_frame(self, base_edge):
+ baxis = base_edge.y
+ if (self.verts[0].index == base_edge.verts[0].index or
+ self.verts[1].index == base_edge.verts[1].index):
+ axis = -self.y
+ elif (self.verts[0].index == base_edge.verts[1].index or
+ self.verts[1].index == base_edge.verts[0].index):
+ axis = self.y
+ else:
+ raise ValueError("edges not connected")
+ if baxis.dot(axis) in (-1, 1):
+ # aligned axis have their up/z aligned
+ up = base_edge.z
+ else:
+ # Get the unit vector dividing the angle (theta) between baxis and
+ # axis in two equal parts
+ h = (baxis + axis)
+ h.normalize()
+ # (cos(theta/2), sin(theta/2) * n) where n is the unit vector of the
+ # axis rotating baxis onto axis
+ q = Quaternion([baxis.dot(h)] + list(baxis.cross(h)))
+ # rotate the base edge's up around the rotation axis (blender
+ # quaternion shortcut:)
+ up = q * base_edge.z
+ self.set_frame(up)
+
+ def calc_vert_planes(self, edges):
+ for v in self.verts:
+ v.calc_planes(edges)
+
+ def bisect_faces(self):
+ n1 = self.bmedge.link_faces[0].normal
+ if len(self.bmedge.link_faces) > 1:
+ n2 = self.bmedge.link_faces[1].normal
+ return (n1 + n2).normalized()
+ return n1
+
+ def calc_simple_frame(self):
+ return self.y.cross(select_up(self.y)).normalized()
+
+ def find_edge_frame(self, sedges):
+ if self.bmedge.link_faces:
+ return self.bisect_faces()
+ if self.verts[0].edges or self.verts[1].edges:
+ edges = list(self.verts[0].edges + self.verts[1].edges)
+ for i in range(len(edges)):
+ edges[i] = sedges[edges[i]]
+ while edges and edges[-1].y.cross(self.y).length < 1e-3:
+ edges.pop()
+ if not edges:
+ return self.calc_simple_frame()
+ n1 = edges[-1].y.cross(self.y).normalized()
+ edges.pop()
+ while edges and edges[-1].y.cross(self.y).cross(n1).length < 1e-3:
+ edges.pop()
+ if not edges:
+ return n1
+ n2 = edges[-1].y.cross(self.y).normalized()
+ return (n1 + n2).normalized()
+ return self.calc_simple_frame()
+
+
+def calc_plane_normal(edge1, edge2):
+ if edge1.verts[0].index == edge2.verts[0].index:
+ axis1 = -edge1.y
+ axis2 = edge2.y
+ elif edge1.verts[1].index == edge2.verts[1].index:
+ axis1 = edge1.y
+ axis2 = -edge2.y
+ elif edge1.verts[0].index == edge2.verts[1].index:
+ axis1 = -edge1.y
+ axis2 = -edge2.y
+ elif edge1.verts[1].index == edge2.verts[0].index:
+ axis1 = edge1.y
+ axis2 = edge2.y
+ else:
+ raise ValueError("edges not connected")
+ # Both axis1 and axis2 are unit vectors, so this will produce a vector
+ # bisects the two, so long as they are not 180 degrees apart (in which
+ # there are infinite solutions).
+ return (axis1 + axis2).normalized()
+
+
+def build_edge_frames(edges):
+ edge_set = set(edges)
+ while edge_set:
+ edge_queue = [edge_set.pop()]
+ edge_queue[0].set_frame(edge_queue[0].find_edge_frame(edges))
+ while edge_queue:
+ current_edge = edge_queue.pop()
+ for i in (0, 1):
+ for e in current_edge.verts[i].edges:
+ edge = edges[e]
+ if edge.x is not None: # edge already processed
+ continue
+ edge_set.remove(edge)
+ edge_queue.append(edge)
+ edge.calc_frame(current_edge)
+
+
+def make_manifold_struts(truss_obj, od, segments):
+ bpy.context.scene.objects.active = truss_obj
+ bpy.ops.object.editmode_toggle()
+ truss_mesh = bmesh.from_edit_mesh(truss_obj.data).copy()
+ bpy.ops.object.editmode_toggle()
+ edges = [None] * len(truss_mesh.edges)
+ for i, e in enumerate(truss_mesh.edges):
+ edges[i] = SEdge(truss_mesh, e)
+ build_edge_frames(edges)
+ verts = []
+ faces = []
+ for e, edge in enumerate(edges):
+ # v, f = make_debug_strut(truss_mesh, e, edge, od)
+ edge.calc_vert_planes(edges)
+ v, f = make_clipped_cylinder(truss_mesh, e, edge, od)
+ verts += v
+ faces += f
+ return verts, faces
+
+
+def make_simple_struts(truss_mesh, id, od, segments, solid, loops):
+ vps = 2
+ if solid:
+ vps *= 2
+ if loops:
+ vps *= 2
+ fps = vps
+ if not solid:
+ fps -= 1
+
+ verts = [None] * len(truss_mesh.edges) * segments * vps
+ faces = [None] * len(truss_mesh.edges) * segments * fps
+ vbase = 0
+ fbase = 0
+ for e in truss_mesh.edges:
+ v1 = truss_mesh.vertices[e.vertices[0]]
+ v2 = truss_mesh.vertices[e.vertices[1]]
+ v, f = make_strut(v1.co, v2.co, id, od, segments, solid, loops)
+ for fv in f:
+ for i in range(len(fv)):
+ fv[i] += vbase
+ for i in range(len(v)):
+ verts[vbase + i] = v[i]
+ for i in range(len(f)):
+ faces[fbase + i] = f[i]
+ # if not base % 12800:
+ # print (base * 100 / len(verts))
+ vbase += vps * segments
+ fbase += fps * segments
+ # print(verts,faces)
+ return verts, faces
+
+
+def create_struts(self, context, id, od, segments, solid, loops, manifold):
+ build_cossin(segments)
+
+ bpy.context.user_preferences.edit.use_global_undo = False
+ for truss_obj in bpy.context.scene.objects:
+ if not truss_obj.select:
+ continue
+ truss_obj.select = False
+ truss_mesh = truss_obj.to_mesh(context.scene, True, 'PREVIEW')
+ if not truss_mesh.edges:
+ continue
+ if manifold:
+ verts, faces = make_manifold_struts(truss_obj, od, segments)
+ else:
+ verts, faces = make_simple_struts(truss_mesh, id, od, segments,
+ solid, loops)
+ mesh = bpy.data.meshes.new("Struts")
+ mesh.from_pydata(verts, [], faces)
+ obj = bpy.data.objects.new("Struts", mesh)
+ bpy.context.scene.objects.link(obj)
+ obj.select = True
+ obj.location = truss_obj.location
+ bpy.context.scene.objects.active = obj
+ mesh.update()
+ bpy.context.user_preferences.edit.use_global_undo = True
+ return {'FINISHED'}
+
+
+class Struts(bpy.types.Operator):
+ """Add one or more struts meshes based on selected truss meshes"""
+ bl_idname = "mesh.generate_struts"
+ bl_label = "Struts"
+ bl_description = """Add one or more struts meshes based on selected truss meshes"""
+ bl_options = {'REGISTER', 'UNDO'}
+
+ id = FloatProperty(name="Inside Diameter",
+ description="diameter of inner surface",
+ min=0.0,
+ soft_min=0.0,
+ max=100,
+ soft_max=100,
+ default=0.04)
+ od = FloatProperty(name="Outside Diameter",
+ description="diameter of outer surface",
+ min=0.001,
+ soft_min=0.001,
+ max=100,
+ soft_max=100,
+ default=0.05)
+ manifold = BoolProperty(name="Manifold",
+ description="Connect struts to form a single solid.",
+ default=False)
+ solid = BoolProperty(name="Solid",
+ description="Create inner surface.",
+ default=False)
+ loops = BoolProperty(name="Loops",
+ description="Create sub-surf friendly loops.",
+ default=False)
+ segments = IntProperty(name="Segments",
+ description="Number of segments around strut",
+ min=3, soft_min=3,
+ max=64, soft_max=64,
+ default=12)
+
+ def execute(self, context):
+ keywords = self.as_keywords()
+ return create_struts(self, context, **keywords)
+
+
+def menu_func(self, context):
+ self.layout.operator(Struts.bl_idname, text="Struts", icon='PLUGIN')
+
+
+def register():
+ bpy.utils.register_module(__name__)
+ bpy.types.INFO_MT_mesh_add.append(menu_func)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/mesh_easylattice.py b/add_advanced_objects/mesh_easylattice.py
new file mode 100644
index 00000000..13512adf
--- /dev/null
+++ b/add_advanced_objects/mesh_easylattice.py
@@ -0,0 +1,381 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+
+bl_info = {
+ "name": "Easy Lattice Object",
+ "author": "Kursad Karatas",
+ "version": (0, 5),
+ "blender": (2, 66, 0),
+ "location": "View3D > Easy Lattice",
+ "description": "Create a lattice for shape editing",
+ "warning": "",
+ "wiki_url": "https://wiki.blender.org/index.php/Easy_Lattice_Editing_Addon",
+ "tracker_url": "https://bitbucket.org/kursad/blender_addons_easylattice/src",
+ "category": "Mesh"}
+
+
+import bpy
+from mathutils import (
+ Matrix,
+ Vector,
+ )
+from bpy.props import (
+ EnumProperty,
+ IntProperty,
+ )
+
+
+# Cleanup
+def modifiersDelete(obj):
+ for mod in obj.modifiers:
+ if mod.name == "latticeeasytemp":
+ try:
+ if mod.object == bpy.data.objects['LatticeEasytTemp']:
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier=mod.name)
+ except:
+ bpy.ops.object.modifier_remove(modifier=mod.name)
+
+
+def modifiersApplyRemove(obj):
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.ops.object.select_pattern(pattern=obj.name, extend=False)
+ bpy.context.scene.objects.active = obj
+
+ for mod in obj.modifiers:
+ if mod.name == "latticeeasytemp":
+ if mod.object == bpy.data.objects['LatticeEasytTemp']:
+ bpy.ops.object.modifier_apply(apply_as='DATA', modifier=mod.name)
+
+
+def latticeDelete(obj):
+ bpy.ops.object.select_all(action='DESELECT')
+ for ob in bpy.context.scene.objects:
+ if "LatticeEasytTemp" in ob.name:
+ ob.select = True
+ bpy.ops.object.delete(use_global=False)
+
+ obj.select = True
+
+
+def createLattice(obj, size, pos, props):
+ # Create lattice and object
+ lat = bpy.data.lattices.new('LatticeEasytTemp')
+ ob = bpy.data.objects.new('LatticeEasytTemp', lat)
+
+ loc, rot, scl = getTransformations(obj)
+
+ # the position comes from the bbox
+ ob.location = pos
+
+ # the size from bbox
+ ob.scale = size
+
+ # the rotation comes from the combined obj world matrix which was converted to euler pairs
+ ob.rotation_euler = buildRot_World(obj)
+
+ ob.show_x_ray = True
+ # Link object to scene
+ scn = bpy.context.scene
+ scn.objects.link(ob)
+ scn.objects.active = ob
+ scn.update()
+
+ # Set lattice attributes
+ lat.interpolation_type_u = props[3]
+ lat.interpolation_type_v = props[3]
+ lat.interpolation_type_w = props[3]
+
+ lat.use_outside = False
+
+ lat.points_u = props[0]
+ lat.points_v = props[1]
+ lat.points_w = props[2]
+
+ return ob
+
+
+def selectedVerts_Grp(obj):
+ vertices = obj.data.vertices
+ selverts = []
+
+ if obj.mode == "EDIT":
+ bpy.ops.object.editmode_toggle()
+
+ for grp in obj.vertex_groups:
+ if "templatticegrp" in grp.name:
+ bpy.ops.object.vertex_group_set_active(group=grp.name)
+ bpy.ops.object.vertex_group_remove()
+
+ tempgroup = obj.vertex_groups.new("templatticegrp")
+
+ for vert in vertices:
+ if vert.select is True:
+ selverts.append(vert)
+ tempgroup.add([vert.index], 1.0, "REPLACE")
+
+ return selverts
+
+
+def getTransformations(obj):
+ rot = obj.rotation_euler
+ loc = obj.location
+ size = obj.scale
+
+ return [loc, rot, size]
+
+
+def findBBox(obj, selvertsarray):
+
+ mat = buildTrnScl_WorldMat(obj)
+ mat_world = obj.matrix_world
+
+ minx, miny, minz = selvertsarray[0].co
+ maxx, maxy, maxz = selvertsarray[0].co
+
+ c = 1
+
+ for c in range(len(selvertsarray)):
+ co = selvertsarray[c].co
+
+ if co.x < minx:
+ minx = co.x
+ if co.y < miny:
+ miny = co.y
+ if co.z < minz:
+ minz = co.z
+
+ if co.x > maxx:
+ maxx = co.x
+ if co.y > maxy:
+ maxy = co.y
+ if co.z > maxz:
+ maxz = co.z
+ c += 1
+
+ minpoint = Vector((minx, miny, minz))
+ maxpoint = Vector((maxx, maxy, maxz))
+
+ # middle point has to be calculated based on the real world matrix
+ middle = ((minpoint + maxpoint) / 2)
+
+ minpoint = mat * minpoint # Calculate only based on loc/scale
+ maxpoint = mat * maxpoint # Calculate only based on loc/scale
+ middle = mat_world * middle # the middle has to be calculated based on the real world matrix
+
+ size = maxpoint - minpoint
+ size = Vector((abs(size.x), abs(size.y), abs(size.z)))
+
+ return [minpoint, maxpoint, size, middle]
+
+
+def buildTrnSclMat(obj):
+ # This function builds a local matrix that encodes translation
+ # and scale and it leaves out the rotation matrix
+ # The rotation is applied at obejct level if there is any
+ mat_trans = Matrix.Translation(obj.location)
+ mat_scale = Matrix.Scale(obj.scale[0], 4, (1, 0, 0))
+ mat_scale *= Matrix.Scale(obj.scale[1], 4, (0, 1, 0))
+ mat_scale *= Matrix.Scale(obj.scale[2], 4, (0, 0, 1))
+
+ mat_final = mat_trans * mat_scale
+
+ return mat_final
+
+
+def buildTrnScl_WorldMat(obj):
+ # This function builds a real world matrix that encodes translation
+ # and scale and it leaves out the rotation matrix
+ # The rotation is applied at obejct level if there is any
+ loc, rot, scl = obj.matrix_world.decompose()
+ mat_trans = Matrix.Translation(loc)
+
+ mat_scale = Matrix.Scale(scl[0], 4, (1, 0, 0))
+ mat_scale *= Matrix.Scale(scl[1], 4, (0, 1, 0))
+ mat_scale *= Matrix.Scale(scl[2], 4, (0, 0, 1))
+
+ mat_final = mat_trans * mat_scale
+
+ return mat_final
+
+
+# Feature use
+def buildRot_WorldMat(obj):
+ # This function builds a real world matrix that encodes rotation
+ # and it leaves out translation and scale matrices
+ loc, rot, scl = obj.matrix_world.decompose()
+ rot = rot.to_euler()
+
+ mat_rot = Matrix.Rotation(rot[0], 4, 'X')
+ mat_rot *= Matrix.Rotation(rot[1], 4, 'Z')
+ mat_rot *= Matrix.Rotation(rot[2], 4, 'Y')
+ return mat_rot
+
+
+def buildTrn_WorldMat(obj):
+ # This function builds a real world matrix that encodes translation
+ # and scale and it leaves out the rotation matrix
+ # The rotation is applied at obejct level if there is any
+ loc, rot, scl = obj.matrix_world.decompose()
+ mat_trans = Matrix.Translation(loc)
+
+ return mat_trans
+
+
+def buildScl_WorldMat(obj):
+ # This function builds a real world matrix that encodes translation
+ # and scale and it leaves out the rotation matrix
+ # The rotation is applied at obejct level if there is any
+ loc, rot, scl = obj.matrix_world.decompose()
+
+ mat_scale = Matrix.Scale(scl[0], 4, (1, 0, 0))
+ mat_scale *= Matrix.Scale(scl[1], 4, (0, 1, 0))
+ mat_scale *= Matrix.Scale(scl[2], 4, (0, 0, 1))
+
+ return mat_scale
+
+
+def buildRot_World(obj):
+ # This function builds a real world rotation values
+ loc, rot, scl = obj.matrix_world.decompose()
+ rot = rot.to_euler()
+
+ return rot
+
+
+def run(lat_props):
+ obj = bpy.context.object
+
+ if obj.type == "MESH":
+ # set global property for the currently active latticed object
+ bpy.types.Scene.activelatticeobject = bpy.props.StringProperty(
+ name="currentlatticeobject",
+ default=""
+ )
+ bpy.types.Scene.activelatticeobject = obj.name
+
+ modifiersDelete(obj)
+ selvertsarray = selectedVerts_Grp(obj)
+ bbox = findBBox(obj, selvertsarray)
+
+ size = bbox[2]
+ pos = bbox[3]
+
+ latticeDelete(obj)
+ lat = createLattice(obj, size, pos, lat_props)
+
+ modif = obj.modifiers.new("latticeeasytemp", "LATTICE")
+ modif.object = lat
+ modif.vertex_group = "templatticegrp"
+
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.ops.object.select_pattern(pattern=lat.name, extend=False)
+ bpy.context.scene.objects.active = lat
+
+ bpy.context.scene.update()
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ if obj.type == "LATTICE":
+ if bpy.types.Scene.activelatticeobject:
+ name = bpy.types.Scene.activelatticeobject
+
+ # Are we in edit lattice mode? If so move on to object mode
+ if obj.mode == "EDIT":
+ bpy.ops.object.editmode_toggle()
+
+ for ob in bpy.context.scene.objects:
+ if ob.name == name: # found the object with the lattice mod
+ object = ob
+ modifiersApplyRemove(object)
+ latticeDelete(obj)
+
+ return
+
+
+def main(context, latticeprops):
+ run(latticeprops)
+
+
+class EasyLattice(bpy.types.Operator):
+ """Adds a Lattice modifier ready to edit"""
+ bl_idname = "object.easy_lattice"
+ bl_label = "Easy Lattice Creator"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+
+ lat_u = IntProperty(
+ name="Lattice u",
+ default=3
+ )
+ lat_w = IntProperty(
+ name="Lattice w",
+ default=3
+ )
+ lat_m = IntProperty(
+ name="Lattice m",
+ default=3
+ )
+ lat_types = (('0', 'KEY_LINEAR', '0'),
+ ('1', 'KEY_CARDINAL', '1'),
+ ('2', 'KEY_BSPLINE', '2'))
+ lat_type = EnumProperty(
+ name="Lattice Type",
+ items=lat_types,
+ default='0'
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def execute(self, context):
+ lat_u = self.lat_u
+ lat_w = self.lat_w
+ lat_m = self.lat_m
+
+ # this is a reference to the "items" used to generate the
+ # enum property
+ lat_type = self.lat_types[int(self.lat_type)][1]
+ lat_props = [lat_u, lat_w, lat_m, lat_type]
+
+ main(context, lat_props)
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ wm = context.window_manager
+ return wm.invoke_props_dialog(self)
+
+
+def menu_draw(self, context):
+ self.layout.operator_context = 'INVOKE_REGION_WIN'
+ self.layout.operator(EasyLattice.bl_idname, "Easy Lattice")
+
+
+def register():
+ bpy.utils.register_class(EasyLattice)
+ bpy.types.VIEW3D_MT_edit_mesh_specials.append(menu_draw)
+
+
+def unregister():
+ bpy.utils.unregister_class(EasyLattice)
+ bpy.types.VIEW3D_MT_edit_mesh_specials.remove(menu_draw)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/object_add_chain.py b/add_advanced_objects/object_add_chain.py
new file mode 100644
index 00000000..57babfd6
--- /dev/null
+++ b/add_advanced_objects/object_add_chain.py
@@ -0,0 +1,170 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+bl_info = {
+ "name": "Add Chain",
+ "author": "Brian Hinton (Nichod)",
+ "version": (0, 1, 2),
+ "blender": (2, 71, 0),
+ "location": "Toolshelf > Create Tab",
+ "description": "Adds Chain with curve guide for easy creation",
+ "warning": "",
+ "wiki_url": "https://wiki.blender.org/index.php/Extensions:2.6/Py/"
+ "Scripts/Object/Add_Chain",
+ "category": "Object",
+}
+
+import bpy
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+
+
+def Add_Chain():
+ # Adds Empty to scene
+ bpy.ops.object.add(type='EMPTY',
+ view_align=False,
+ enter_editmode=False,
+ location=(0, 0, 0),
+ rotation=(0, 0, 0),
+ )
+
+ # Changes name of Empty to rot_link adds variable emp
+ emp = bpy.context.object
+ emp.name = "rot_link"
+
+ # Rotate emp ~ 90 degrees
+ emp.rotation_euler = [1.570796, 0, 0]
+
+ # Adds Curve Path to scene
+ bpy.ops.curve.primitive_nurbs_path_add(view_align=False,
+ enter_editmode=False,
+ location=(0, 0, 0),
+ rotation=(0, 0, 0),
+ )
+
+ # Change Curve name to deform adds variable curv
+ curv = bpy.context.object
+ curv.name = "deform"
+
+ # Inserts Torus primitive
+ bpy.ops.mesh.primitive_torus_add(major_radius=1,
+ minor_radius=0.25,
+ major_segments=12,
+ minor_segments=4,
+ abso_major_rad=1,
+ abso_minor_rad=0.5,
+ )
+
+ # Positions Torus primitive to center of scene
+ bpy.context.active_object.location = 0.0, 0.0, 0.0
+
+ # Reseting Torus rotation in case of 'Align to view' option enabled
+ bpy.context.active_object.rotation_euler = 0.0, 0.0, 0.0
+
+ # Changes Torus name to chain adds variable tor
+ tor = bpy.context.object
+ tor.name = "chain"
+
+ # Adds Array Modifier to tor
+ bpy.ops.object.modifier_add(type='ARRAY')
+
+ # Adds subsurf modifier tor
+ bpy.ops.object.modifier_add(type='SUBSURF')
+
+ # Smooths tor
+ bpy.ops.object.shade_smooth()
+
+ # Select curv
+ sce = bpy.context.scene
+ sce.objects.active = curv
+
+ # Toggle into editmode
+ bpy.ops.object.editmode_toggle()
+
+ # TODO, may be better to move objects directly.
+ # Translate curve object
+ bpy.ops.transform.translate(value=(2, 0, 0),
+ constraint_axis=(True, False, False),
+ constraint_orientation='GLOBAL',
+ mirror=False,
+ proportional='DISABLED',
+ proportional_edit_falloff='SMOOTH',
+ proportional_size=1,
+ snap=False,
+ snap_target='CLOSEST',
+ snap_point=(0, 0, 0),
+ snap_align=False,
+ snap_normal=(0, 0, 0),
+ release_confirm=False,
+ )
+
+ # Toggle into objectmode
+ bpy.ops.object.editmode_toggle()
+
+ # Select tor or chain
+ sce.objects.active = tor
+
+ # Selects Array Modifier for editing
+ array = tor.modifiers['Array']
+
+ # Change Array Modifier Parameters
+ array.fit_type = 'FIT_CURVE'
+ array.curve = curv
+ array.offset_object = emp
+ array.use_object_offset = True
+ array.relative_offset_displace = 0.549, 0.0, 0.0
+
+ # Add curve modifier
+ bpy.ops.object.modifier_add(type='CURVE')
+
+ # Selects Curve Modifier for editing
+ cur = tor.modifiers['Curve']
+
+ # Change Curve Modifier Parameters
+ cur.object = curv
+
+
+class AddChain(Operator):
+ bl_idname = "mesh.primitive_chain_add"
+ bl_label = "Add Chain"
+ bl_description = ("Create a Chain segment with helper objects controlling modifiers:\n"
+ "1) A Curve Modifier Object (deform) for the length and shape,\n"
+ "Edit the Path to extend Chain Length\n"
+ "2) An Empty (rot_link) as an Array Offset for rotation")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ Add_Chain()
+
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_module(__name__)
+ pass
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+ pass
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/object_laplace_lightning.py b/add_advanced_objects/object_laplace_lightning.py
new file mode 100644
index 00000000..ceaf6cd6
--- /dev/null
+++ b/add_advanced_objects/object_laplace_lightning.py
@@ -0,0 +1,1370 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# NOTE Needs cleanup, reorganizing, make prints optional
+
+bl_info = {
+ "name": "Laplacian Lightning",
+ "author": "teldredge",
+ "version": (0, 2, 7),
+ "blender": (2, 71, 0),
+ "location": "View3D > Toolshelf > Create Tab",
+ "description": "Lightning mesh generator using laplacian growth algorithm",
+ "warning": "Beta",
+ "wiki_url": "http://www.funkboxing.com/wordpress/?p=301",
+ "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
+ "category": "Object"}
+
+# BLENDER LAPLACIAN LIGHTNING
+# teldredge
+# www.funkboxing.com
+# https://developer.blender.org/T27189
+
+# using algorithm from
+# FAST SIMULATION OF LAPLACIAN GROWTH (FSLG)
+# http://gamma.cs.unc.edu/FRAC/
+
+# and a few ideas ideas from
+# FAST ANIMATION OF LIGHTNING USING AN ADAPTIVE MESH (FALUAM)
+# http://gamma.cs.unc.edu/FAST_LIGHTNING/
+
+
+"""
+----- RELEASE LOG/NOTES/PONTIFICATIONS -----
+v0.1.0 - 04.11.11
+ basic generate functions and UI
+ object creation report (Custom Properties: FSLG_REPORT)
+v0.2.0 - 04.15.11
+ started spelling laplacian right.
+ add curve function (not in UI) ...twisting problem
+ classify stroke by MAIN path, h-ORDER paths, TIP paths
+ jitter cells for mesh creation
+ add materials if present
+v0.2.1 - 04.16.11
+ mesh classification speedup
+v0.2.2 - 04.21.11
+ fxns to write/read array to file
+ restrict growth to insulator cells (object bounding box)
+ origin/ground defineable by object
+ gridunit more like 'resolution'
+v0.2.3 - 04.24.11
+ cloud attractor object (termintates loop if hit)
+ secondary path orders (hOrder) disabled in UI (set to 1)
+v0.2.4 - 04.26.11
+ fixed object selection in UI
+ will not run if required object not selected
+ moved to view 3d > toolbox
+v0.2.5 - 05.08.11
+ testing for 2.57b
+ single mesh output (for build modifier)
+ speedups (dist fxn)
+v0.2.6 - 06.20.11
+ scale/pos on 'write to cubes' works now
+ if origin obj is mesh, uses all verts as initial charges
+ semi-helpful tooltips
+ speedups, faster dedupe fxn, faster classification
+ use any shape mesh obj as insulator mesh
+ must have rot=0, scale=1, origin set to geometry
+ often fails to block bolt with curved/complex shapes
+ separate single and multi mesh creation
+v0.2.7 - 01.05.13
+ fixed the issue that prevented enabling the add-on
+ fixed makeMeshCube fxn
+ disabled visualization for voxels
+
+v0.x -
+ -prevent create_setup_objects from generating duplicates
+ -fix vis fxn to only buildCPGraph once for VM or VS
+ -improve list fxns (rid of ((x,y,z),w) and use (x,y,z,w)), use 'sets'
+ -create python cmodule for a few of most costly fxns
+ i have pretty much no idea how to do this yet
+ -cloud and insulator can be groups of MESH objs
+ -text output, possibly to save on interrupt, allow continue from text
+ -?hook modifiers from tips->sides->main, weight w/ vert groups
+ -user defined 'attractor' path
+ -fix add curve function
+ -animated arcs via. ionization path
+ -environment map boundary conditions - requires Eqn. 15 from FSLG.
+ -assign wattage at each segment for HDRI
+ -?default settings for -lightning, -teslacoil, -spark/arc
+ -fix hOrder functionality
+ -multiple 'MAIN' brances for non-lightning discharges
+ -n-symmetry option, create mirror images, snowflakes, etc...
+"""
+
+import bpy
+import time
+import random
+from math import sqrt
+from mathutils import Vector
+import struct
+import bisect
+import os.path
+notZero = 0.0000000001
+winmgr = bpy.context.window_manager
+
+
+# UTILITY FXNS #
+
+def within(x, y, d):
+ # CHECK IF x-d <= y <= x+d
+ if x - d <= y and x + d >= y:
+ return True
+ else:
+ return False
+
+
+def dist(ax, ay, az, bx, by, bz):
+ dv = Vector((ax, ay, az)) - Vector((bx, by, bz))
+ d = dv.length
+ return d
+
+
+def splitList(aList, idx):
+ ll = []
+ for x in aList:
+ ll.append(x[idx])
+ return ll
+
+
+def splitListCo(aList):
+ ll = []
+ for p in aList:
+ ll.append((p[0], p[1], p[2]))
+ return ll
+
+
+def getLowHigh(aList):
+ tLow = aList[0]
+ tHigh = aList[0]
+ for a in aList:
+ if a < tLow:
+ tLow = a
+ if a > tHigh:
+ tHigh = a
+ return tLow, tHigh
+
+
+def weightedRandomChoice(aList):
+ tL = []
+ tweight = 0
+ for a in range(len(aList)):
+ idex = a
+ weight = aList[a]
+ if weight > 0.0:
+ tweight += weight
+ tL.append((tweight, idex))
+ i = bisect.bisect(tL, (random.uniform(0, tweight), None))
+ r = tL[i][1]
+ return r
+
+
+def getStencil3D_26(x, y, z):
+ nL = []
+ for xT in range(x - 1, x + 2):
+ for yT in range(y - 1, y + 2):
+ for zT in range(z - 1, z + 2):
+ nL.append((xT, yT, zT))
+ nL.remove((x, y, z))
+ return nL
+
+
+def jitterCells(aList, jit):
+ j = jit / 2
+ bList = []
+ for a in aList:
+ ax = a[0] + random.uniform(-j, j)
+ ay = a[1] + random.uniform(-j, j)
+ az = a[2] + random.uniform(-j, j)
+ bList.append((ax, ay, az))
+ return bList
+
+
+def deDupe(seq, idfun=None):
+ # THANKS TO THIS GUY - http://www.peterbe.com/plog/uniqifiers-benchmark
+ if idfun is None:
+ def idfun(x):
+ return x
+ seen = {}
+ result = []
+ for item in seq:
+ marker = idfun(item)
+ if marker in seen:
+ continue
+ seen[marker] = 1
+ result.append(item)
+ return result
+
+
+# VISUALIZATION FXNS #
+
+def writeArrayToVoxel(arr, filename):
+ gridS = 64
+ half = int(gridS / 2)
+ bitOn = 255
+ aGrid = [[[0 for z in range(gridS)] for y in range(gridS)] for x in range(gridS)]
+ for a in arr:
+ try:
+ aGrid[a[0] + half][a[1] + half][a[2] + half] = bitOn
+ except:
+ print('Particle beyond voxel domain')
+ file = open(filename, "wb")
+ for z in range(gridS):
+ for y in range(gridS):
+ for x in range(gridS):
+ file.write(struct.pack('B', aGrid[x][y][z]))
+ file.flush()
+ file.close()
+
+
+def writeArrayToFile(arr, filename):
+ file = open(filename, "w")
+ for a in arr:
+ tstr = str(a[0]) + ',' + str(a[1]) + ',' + str(a[2]) + '\n'
+ file.write(tstr)
+ file.close
+
+
+def readArrayFromFile(filename):
+ file = open(filename, "r")
+ arr = []
+ for f in file:
+ pt = f[0:-1].split(',')
+ arr.append((int(pt[0]), int(pt[1]), int(pt[2])))
+ return arr
+
+
+def makeMeshCube_OLD(msize):
+ msize = msize / 2
+ mmesh = bpy.data.meshes.new('q')
+ mmesh.vertices.add(8)
+ mmesh.vertices[0].co = [-msize, -msize, -msize]
+ mmesh.vertices[1].co = [-msize, msize, -msize]
+ mmesh.vertices[2].co = [msize, msize, -msize]
+ mmesh.vertices[3].co = [msize, -msize, -msize]
+ mmesh.vertices[4].co = [-msize, -msize, msize]
+ mmesh.vertices[5].co = [-msize, msize, msize]
+ mmesh.vertices[6].co = [msize, msize, msize]
+ mmesh.vertices[7].co = [msize, -msize, msize]
+ mmesh.faces.add(6)
+ mmesh.faces[0].vertices_raw = [0, 1, 2, 3]
+ mmesh.faces[1].vertices_raw = [0, 4, 5, 1]
+ mmesh.faces[2].vertices_raw = [2, 1, 5, 6]
+ mmesh.faces[3].vertices_raw = [3, 2, 6, 7]
+ mmesh.faces[4].vertices_raw = [0, 3, 7, 4]
+ mmesh.faces[5].vertices_raw = [5, 4, 7, 6]
+ mmesh.update(calc_edges=True)
+
+ return(mmesh)
+
+
+def makeMeshCube(msize):
+ m2 = msize / 2
+ # verts = [(0,0,0),(0,5,0),(5,5,0),(5,0,0),(0,0,5),(0,5,5),(5,5,5),(5,0,5)]
+ verts = [(-m2, -m2, -m2), (-m2, m2, -m2), (m2, m2, -m2), (m2, -m2, -m2),
+ (-m2, -m2, m2), (-m2, m2, m2), (m2, m2, m2), (m2, -m2, m2)]
+ faces = [(0, 1, 2, 3), (4, 5, 6, 7), (0, 4, 5, 1), (1, 5, 6, 2), (2, 6, 7, 3), (3, 7, 4, 0)]
+
+ # Define mesh and object
+ mmesh = bpy.data.meshes.new("Cube")
+ # mobject = bpy.data.objects.new("Cube", mmesh)
+
+ # Set location and scene of object
+ # mobject.location = bpy.context.scene.cursor_location
+ # bpy.context.scene.objects.link(mobject)
+
+ # Create mesh
+ mmesh.from_pydata(verts, [], faces)
+ mmesh.update(calc_edges=True)
+ return(mmesh)
+
+
+def writeArrayToCubes(arr, gridBU, orig, cBOOL=False, jBOOL=True):
+ for a in arr:
+ x = a[0]
+ y = a[1]
+ z = a[2]
+ me = makeMeshCube(gridBU)
+ ob = bpy.data.objects.new('xCUBE', me)
+ ob.location.x = (x * gridBU) + orig[0]
+ ob.location.y = (y * gridBU) + orig[1]
+ ob.location.z = (z * gridBU) + orig[2]
+ if cBOOL: # MOSTLY UNUSED
+ # POS+BLUE, NEG-RED, ZERO:BLACK
+ col = (1.0, 1.0, 1.0, 1.0)
+ if a[3] == 0:
+ col = (0.0, 0.0, 0.0, 1.0)
+ if a[3] < 0:
+ col = (-a[3], 0.0, 0.0, 1.0)
+ if a[3] > 0:
+ col = (0.0, 0.0, a[3], 1.0)
+ ob.color = col
+ bpy.context.scene.objects.link(ob)
+ bpy.context.scene.update()
+ if jBOOL:
+ # SELECTS ALL CUBES w/ ?bpy.ops.object.join() b/c
+ # CAN'T JOIN ALL CUBES TO A SINGLE MESH RIGHT... ARGH...
+ for q in bpy.context.scene.objects:
+ q.select = False
+ if q.name[0:5] == 'xCUBE':
+ q.select = True
+ bpy.context.scene.objects.active = q
+
+
+def addVert(ob, pt, conni=-1):
+ mmesh = ob.data
+ mmesh.vertices.add(1)
+ vcounti = len(mmesh.vertices) - 1
+ mmesh.vertices[vcounti].co = [pt[0], pt[1], pt[2]]
+ if conni > -1:
+ mmesh.edges.add(1)
+ ecounti = len(mmesh.edges) - 1
+ mmesh.edges[ecounti].vertices = [conni, vcounti]
+ mmesh.update()
+
+
+def addEdge(ob, va, vb):
+ mmesh = ob.data
+ mmesh.edges.add(1)
+ ecounti = len(mmesh.edges) - 1
+ mmesh.edges[ecounti].vertices = [va, vb]
+ mmesh.update()
+
+
+def newMesh(mname):
+ mmesh = bpy.data.meshes.new(mname)
+ omesh = bpy.data.objects.new(mname, mmesh)
+ bpy.context.scene.objects.link(omesh)
+ return omesh
+
+
+def writeArrayToMesh(mname, arr, gridBU, rpt=None):
+ mob = newMesh(mname)
+ mob.scale = (gridBU, gridBU, gridBU)
+ if rpt:
+ addReportProp(mob, rpt)
+ addVert(mob, arr[0], -1)
+ for ai in range(1, len(arr)):
+ a = arr[ai]
+ addVert(mob, a, ai - 1)
+ return mob
+
+
+# OUT OF ORDER - SOME PROBLEM WITH IT ADDING (0,0,0)
+def writeArrayToCurves(cname, arr, gridBU, bd=.05, rpt=None):
+ cur = bpy.data.curves.new('fslg_curve', 'CURVE')
+ cur.use_fill_front = False
+ cur.use_fill_back = False
+ cur.bevel_depth = bd
+ cur.bevel_resolution = 2
+ cob = bpy.data.objects.new(cname, cur)
+ cob.scale = (gridBU, gridBU, gridBU)
+ if rpt:
+ addReportProp(cob, rpt)
+ bpy.context.scene.objects.link(cob)
+ cur.splines.new('BEZIER')
+ cspline = cur.splines[0]
+ div = 1 # SPACING FOR HANDLES (2 - 1/2 WAY, 1 - NEXT BEZIER)
+ for a in range(len(arr)):
+ cspline.bezier_points.add(1)
+ bp = cspline.bezier_points[len(cspline.bezier_points) - 1]
+ if a - 1 < 0:
+ hL = arr[a]
+ else:
+ hx = arr[a][0] - ((arr[a][0] - arr[a - 1][0]) / div)
+ hy = arr[a][1] - ((arr[a][1] - arr[a - 1][1]) / div)
+ hz = arr[a][2] - ((arr[a][2] - arr[a - 1][2]) / div)
+ hL = (hx, hy, hz)
+
+ if a + 1 > len(arr) - 1:
+ hR = arr[a]
+ else:
+ hx = arr[a][0] + ((arr[a + 1][0] - arr[a][0]) / div)
+ hy = arr[a][1] + ((arr[a + 1][1] - arr[a][1]) / div)
+ hz = arr[a][2] + ((arr[a + 1][2] - arr[a][2]) / div)
+ hR = (hx, hy, hz)
+ bp.co = arr[a]
+ bp.handle_left = hL
+ bp.handle_right = hR
+
+
+def addArrayToMesh(mob, arr):
+ addVert(mob, arr[0], -1)
+ mmesh = mob.data
+ vcounti = len(mmesh.vertices) - 1
+ for ai in range(1, len(arr)):
+ a = arr[ai]
+ addVert(mob, a, len(mmesh.vertices) - 1)
+
+
+def addMaterial(ob, matname):
+ mat = bpy.data.materials[matname]
+ ob.active_material = mat
+
+
+def writeStokeToMesh(arr, jarr, MAINi, HORDERi, TIPSi, orig, gs, rpt=None):
+ # MAIN BRANCH
+ print(' WRITING MAIN BRANCH')
+ llmain = []
+ for x in MAINi:
+ llmain.append(jarr[x])
+ mob = writeArrayToMesh('la0MAIN', llmain, gs)
+ mob.location = orig
+
+ # hORDER BRANCHES
+ for hOi in range(len(HORDERi)):
+ print(' WRITING ORDER', hOi)
+ hO = HORDERi[hOi]
+ hob = newMesh('la1H' + str(hOi))
+
+ for y in hO:
+ llHO = []
+ for x in y:
+ llHO.append(jarr[x])
+ addArrayToMesh(hob, llHO)
+ hob.scale = (gs, gs, gs)
+ hob.location = orig
+
+ # TIPS
+ print(' WRITING TIP PATHS')
+ tob = newMesh('la2TIPS')
+ for y in TIPSi:
+ llt = []
+ for x in y:
+ llt.append(jarr[x])
+ addArrayToMesh(tob, llt)
+ tob.scale = (gs, gs, gs)
+ tob.location = orig
+
+ # ADD MATERIALS TO OBJECTS (IF THEY EXIST)
+ try:
+ addMaterial(mob, 'edgeMAT-h0')
+ addMaterial(hob, 'edgeMAT-h1')
+ addMaterial(tob, 'edgeMAT-h2')
+ print(' ADDED MATERIALS')
+ except:
+ print(' MATERIALS NOT FOUND')
+
+ # ADD GENERATION REPORT TO ALL MESHES
+ if rpt:
+ addReportProp(mob, rpt)
+ addReportProp(hob, rpt)
+ addReportProp(tob, rpt)
+
+
+def writeStokeToSingleMesh(arr, jarr, orig, gs, mct, rpt=None):
+ sgarr = buildCPGraph(arr, mct)
+ llALL = []
+
+ Aob = newMesh('laALL')
+ for pt in jarr:
+ addVert(Aob, pt)
+ for cpi in range(len(sgarr)):
+ ci = sgarr[cpi][0]
+ pi = sgarr[cpi][1]
+ addEdge(Aob, pi, ci)
+ Aob.location = orig
+ Aob.scale = ((gs, gs, gs))
+
+ if rpt:
+ addReportProp(Aob, rpt)
+
+
+def visualizeArray(cg, oob, gs, vm, vs, vc, vv, rst):
+ # IN: (cellgrid, origin, gridscale,
+ # mulimesh, single mesh, cubes, voxels, report sting)
+ origin = oob.location
+
+ # DEAL WITH VERT MULTI-ORIGINS
+ oct = 2
+ if oob.type == 'MESH':
+ oct = len(oob.data.vertices)
+
+ # JITTER CELLS
+ if vm or vs:
+ cjarr = jitterCells(cg, 1)
+
+ if vm: # WRITE ARRAY TO MULTI MESH
+
+ aMi, aHi, aTi = classifyStroke(cg, oct, winmgr.HORDER)
+ print(':::WRITING TO MULTI-MESH')
+ writeStokeToMesh(cg, cjarr, aMi, aHi, aTi, origin, gs, rst)
+ print(':::MULTI-MESH WRITTEN')
+
+ if vs: # WRITE TO SINGLE MESH
+ print(':::WRITING TO SINGLE MESH')
+ writeStokeToSingleMesh(cg, cjarr, origin, gs, oct, rst)
+ print(':::SINGLE MESH WRITTEN')
+
+ if vc: # WRITE ARRAY TO CUBE OBJECTS
+ print(':::WRITING TO CUBES')
+ writeArrayToCubes(cg, gs, origin)
+ print(':::CUBES WRITTEN')
+
+ if vv: # WRITE ARRAY TO VOXEL DATA FILE
+ print(':::WRITING TO VOXELS')
+ fname = "FSLGvoxels.raw"
+ path = os.path.dirname(bpy.data.filepath)
+ writeArrayToVoxel(cg, path + "\\" + fname)
+ print(':::VOXEL DATA WRITTEN TO - ', path + "\\" + fname)
+
+ # READ/WRITE ARRAY TO FILE (MIGHT NOT BE NECESSARY)
+ # tfile = 'c:\\testarr.txt'
+ # writeArrayToFile(cg, tfile)
+ # cg = readArrayFromFile(tfile)
+
+ # READ/WRITE ARRAY TO CURVES (OUT OF ORDER)
+ # writeArrayToCurves('laMAIN', llmain, .10, .25)
+
+
+# ALGORITHM FXNS #
+# FROM FALUAM PAPER #
+# PLUS SOME STUFF I MADE UP #
+
+def buildCPGraph(arr, sti=2):
+ # IN -XYZ ARRAY AS BUILT BY GENERATOR
+ # OUT -[(CHILDindex, PARENTindex)]
+ # sti - start index, 2 for Empty, len(me.vertices) for Mesh
+ sgarr = []
+ sgarr.append((1, 0))
+ for ai in range(sti, len(arr)):
+ cs = arr[ai]
+ cpts = arr[0:ai]
+ cslap = getStencil3D_26(cs[0], cs[1], cs[2])
+
+ for nc in cslap:
+ ct = cpts.count(nc)
+ if ct > 0:
+ cti = cpts.index(nc)
+ sgarr.append((ai, cti))
+ return sgarr
+
+
+def buildCPGraph_WORKINPROGRESS(arr, sti=2):
+ # IN -XYZ ARRAY AS BUILT BY GENERATOR
+ # OUT -[(CHILDindex, PARENTindex)]
+ # sti - start index, 2 for Empty, len(me.vertices) for Mesh
+ sgarr = []
+ sgarr.append((1, 0))
+ ctix = 0
+ for ai in range(sti, len(arr)):
+ cs = arr[ai]
+ # cpts = arr[0:ai]
+ cpts = arr[ctix:ai]
+ cslap = getStencil3D_26(cs[0], cs[1], cs[2])
+ for nc in cslap:
+ ct = cpts.count(nc)
+ if ct > 0:
+ # cti = cpts.index(nc)
+ cti = ctix + cpts.index(nc)
+ ctix = cpts.index(nc)
+
+ sgarr.append((ai, cti))
+ return sgarr
+
+
+def findChargePath(oc, fc, ngraph, restrict=[], partial=True):
+ # oc -ORIGIN CHARGE INDEX, fc -FINAL CHARGE INDEX
+ # ngraph -NODE GRAPH, restrict- INDEX OF SITES CANNOT TRAVERSE
+ # partial -RETURN PARTIAL PATH IF RESTRICTION ENCOUNTERD
+ cList = splitList(ngraph, 0)
+ pList = splitList(ngraph, 1)
+ aRi = []
+ cNODE = fc
+ for x in range(len(ngraph)):
+ pNODE = pList[cList.index(cNODE)]
+ aRi.append(cNODE)
+ cNODE = pNODE
+ npNODECOUNT = cList.count(pNODE)
+ if cNODE == oc: # STOP IF ORIGIN FOUND
+ aRi.append(cNODE) # RETURN PATH
+ return aRi
+ if npNODECOUNT == 0: # STOP IF NO PARENTS
+ return [] # RETURN []
+ if pNODE in restrict: # STOP IF PARENT IS IN RESTRICTION
+ if partial: # RETURN PARTIAL OR []
+ aRi.append(cNODE)
+ return aRi
+ else:
+ return []
+
+
+def findTips(arr):
+ lt = []
+ for ai in arr[0: len(arr) - 1]:
+ a = ai[0]
+ cCOUNT = 0
+ for bi in arr:
+ b = bi[1]
+ if a == b:
+ cCOUNT += 1
+ if cCOUNT == 0:
+ lt.append(a)
+ return lt
+
+
+def findChannelRoots(path, ngraph, restrict=[]):
+ roots = []
+ for ai in range(len(ngraph)):
+ chi = ngraph[ai][0]
+ par = ngraph[ai][1]
+ if par in path and chi not in path and chi not in restrict:
+ roots.append(par)
+ droots = deDupe(roots)
+ return droots
+
+
+def findChannels(roots, tips, ngraph, restrict):
+ cPATHS = []
+ for ri in range(len(roots)):
+ r = roots[ri]
+ sL = 1
+ sPATHi = []
+ for ti in range(len(tips)):
+ t = tips[ti]
+ if t < r:
+ continue
+ tPATHi = findChargePath(r, t, ngraph, restrict, False)
+ tL = len(tPATHi)
+ if tL > sL:
+ if countChildrenOnPath(tPATHi, ngraph) > 1:
+ sL = tL
+ sPATHi = tPATHi
+ tTEMP = t
+ tiTEMP = ti
+ if len(sPATHi) > 0:
+ print(' found path/idex from', ri, 'of',
+ len(roots), 'possible | tips:', tTEMP, tiTEMP)
+ cPATHS.append(sPATHi)
+ tips.remove(tTEMP)
+ return cPATHS
+
+
+def findChannels_WORKINPROGRESS(roots, ttips, ngraph, restrict):
+ cPATHS = []
+ tips = list(ttips)
+ for ri in range(len(roots)):
+ r = roots[ri]
+ sL = 1
+ sPATHi = []
+ tipREMOVE = [] # CHECKED TIP INDEXES, TO BE REMOVED FOR NEXT LOOP
+ for ti in range(len(tips)):
+ t = tips[ti]
+ # print('-CHECKING RT/IDEX:', r, ri, 'AGAINST TIP', t, ti)
+ # if t < r: continue
+ if ti < ri:
+ continue
+ tPATHi = findChargePath(r, t, ngraph, restrict, False)
+ tL = len(tPATHi)
+ if tL > sL:
+ if countChildrenOnPath(tPATHi, ngraph) > 1:
+ sL = tL
+ sPATHi = tPATHi
+ tTEMP = t
+ tiTEMP = ti
+ if tL > 0:
+ tipREMOVE.append(t)
+ if len(sPATHi) > 0:
+ print(' found path from root idex', ri, 'of',
+ len(roots), 'possible roots | #oftips=', len(tips))
+ cPATHS.append(sPATHi)
+ for q in tipREMOVE:
+ tips.remove(q)
+
+ return cPATHS
+
+
+def countChildrenOnPath(aPath, ngraph, quick=True):
+ # RETURN HOW MANY BRANCHES
+ # COUNT WHEN NODE IS A PARENT >1 TIMES
+ # quick -STOP AND RETURN AFTER FIRST
+ cCOUNT = 0
+ pList = splitList(ngraph, 1)
+ for ai in range(len(aPath) - 1):
+ ap = aPath[ai]
+ pc = pList.count(ap)
+ if quick and pc > 1:
+ return pc
+ return cCOUNT
+
+
+# CLASSIFY CHANNELS INTO 'MAIN', 'hORDER/SECONDARY' and 'SIDE'
+def classifyStroke(sarr, mct, hORDER=1):
+ print(':::CLASSIFYING STROKE')
+ # BUILD CHILD/PARENT GRAPH (INDEXES OF sarr)
+ sgarr = buildCPGraph(sarr, mct)
+
+ # FIND MAIN CHANNEL
+ print(' finding MAIN')
+ oCharge = sgarr[0][1]
+ fCharge = sgarr[len(sgarr) - 1][0]
+ aMAINi = findChargePath(oCharge, fCharge, sgarr)
+
+ # FIND TIPS
+ print(' finding TIPS')
+ aTIPSi = findTips(sgarr)
+
+ # FIND hORDER CHANNEL ROOTS
+ # hCOUNT = ORDERS BEWTEEN MAIN and SIDE/TIPS
+ # !!!STILL BUGGY!!!
+ hRESTRICT = list(aMAINi) # ADD TO THIS AFTER EACH TIME
+ allHPATHSi = [] # ALL hO PATHS: [[h0], [h1]...]
+ curPATHSi = [aMAINi] # LIST OF PATHS FIND ROOTS ON
+ for h in range(hORDER):
+ allHPATHSi.append([])
+ for pi in range(len(curPATHSi)): # LOOP THROUGH ALL PATHS IN THIS ORDER
+ p = curPATHSi[pi]
+ # GET ROOTS FOR THIS PATH
+ aHROOTSi = findChannelRoots(p, sgarr, hRESTRICT)
+ print(' found', len(aHROOTSi), 'roots in ORDER', h, ':#paths:', len(curPATHSi))
+ # GET CHANNELS FOR THESE ROOTS
+ if len(aHROOTSi) == 0:
+ print('NO ROOTS FOR FOUND FOR CHANNEL')
+ aHPATHSi = []
+ continue
+ else:
+ aHPATHSiD = findChannels(aHROOTSi, aTIPSi, sgarr, hRESTRICT)
+ aHPATHSi = aHPATHSiD
+ allHPATHSi[h] += aHPATHSi
+ # SET THESE CHANNELS AS RESTRICTIONS FOR NEXT ITERATIONS
+ for hri in aHPATHSi:
+ hRESTRICT += hri
+ curPATHSi = aHPATHSi
+
+ # SIDE BRANCHES, FINAL ORDER OF HEIRARCHY
+ # FROM TIPS THAT ARE NOT IN AN EXISTING PATH
+ # BACK TO ANY OTHER POINT THAT IS ALREADY ON A PATH
+ aDRAWNi = []
+ aDRAWNi += aMAINi
+ for oH in allHPATHSi:
+ for o in oH:
+ aDRAWNi += o
+ aTPATHSi = []
+ for a in aTIPSi:
+ if a not in aDRAWNi:
+ aPATHi = findChargePath(oCharge, a, sgarr, aDRAWNi)
+ aDRAWNi += aPATHi
+ aTPATHSi.append(aPATHi)
+
+ return aMAINi, allHPATHSi, aTPATHSi
+
+
+def voxelByVertex(ob, gs):
+ # 'VOXELIZES' VERTS IN A MESH TO LIST [(x,y,z),(x,y,z)]
+ # W/ RESPECT GSCALE AND OB ORIGIN (B/C SHOULD BE ORIGIN OBJ)
+ orig = ob.location
+ ll = []
+ for v in ob.data.vertices:
+ x = int(v.co.x / gs)
+ y = int(v.co.y / gs)
+ z = int(v.co.z / gs)
+ ll.append((x, y, z))
+ return ll
+
+
+def voxelByRays(ob, orig, gs):
+ # MESH INTO A 3DGRID W/ RESPECT GSCALE AND BOLT ORIGIN
+ # -DOES NOT TAKE OBJECT ROTATION/SCALE INTO ACCOUNT
+ # -THIS IS A HORRIBLE, INEFFICIENT FUNCTION
+ # MAYBE THE RAYCAST/GRID THING ARE A BAD IDEA. BUT I
+ # HAVE TO 'VOXELIZE THE OBJECT W/ RESCT TO GSCALE/ORIGIN
+ bbox = ob.bound_box
+ bbxL = bbox[0][0]
+ bbxR = bbox[4][0]
+ bbyL = bbox[0][1]
+ bbyR = bbox[2][1]
+ bbzL = bbox[0][2]
+ bbzR = bbox[1][2]
+ xct = int((bbxR - bbxL) / gs)
+ yct = int((bbyR - bbyL) / gs)
+ zct = int((bbzR - bbzL) / gs)
+ xs = int(xct / 2)
+ ys = int(yct / 2)
+ zs = int(zct / 2)
+ print(' CASTING', xct, '/', yct, '/', zct, 'cells, total:', xct * yct * zct, 'in obj-', ob.name)
+ ll = []
+ rc = 100 # DISTANCE TO CAST FROM
+ # RAYCAST TOP/BOTTOM
+ print(' RAYCASTING TOP/BOTTOM')
+ for x in range(xct):
+ for y in range(yct):
+ xco = bbxL + (x * gs)
+ yco = bbyL + (y * gs)
+ v1 = ((xco, yco, rc))
+ v2 = ((xco, yco, -rc))
+ vz1 = ob.ray_cast(v1, v2)
+ vz2 = ob.ray_cast(v2, v1)
+ if vz1[2] != -1:
+ ll.append((x - xs, y - ys, int(vz1[0][2] * (1 / gs))))
+ if vz2[2] != -1:
+ ll.append((x - xs, y - ys, int(vz2[0][2] * (1 / gs))))
+ # RAYCAST FRONT/BACK
+ print(' RAYCASTING FRONT/BACK')
+ for x in range(xct):
+ for z in range(zct):
+ xco = bbxL + (x * gs)
+ zco = bbzL + (z * gs)
+ v1 = ((xco, rc, zco))
+ v2 = ((xco, -rc, zco))
+ vy1 = ob.ray_cast(v1, v2)
+ vy2 = ob.ray_cast(v2, v1)
+ if vy1[2] != -1:
+ ll.append((x - xs, int(vy1[0][1] * (1 / gs)), z - zs))
+ if vy2[2] != -1:
+ ll.append((x - xs, int(vy2[0][1] * (1 / gs)), z - zs))
+ # RAYCAST LEFT/RIGHT
+ print(' RAYCASTING LEFT/RIGHT')
+ for y in range(yct):
+ for z in range(zct):
+ yco = bbyL + (y * gs)
+ zco = bbzL + (z * gs)
+ v1 = ((rc, yco, zco))
+ v2 = ((-rc, yco, zco))
+ vx1 = ob.ray_cast(v1, v2)
+ vx2 = ob.ray_cast(v2, v1)
+ if vx1[2] != -1:
+ ll.append((int(vx1[0][0] * (1 / gs)), y - ys, z - zs))
+ if vx2[2] != -1:
+ ll.append((int(vx2[0][0] * (1 / gs)), y - ys, z - zs))
+
+ # ADD IN NEIGHBORS SO BOLT WONT GO THRU
+ nlist = []
+ for l in ll:
+ nl = getStencil3D_26(l[0], l[1], l[2])
+ nlist += nl
+
+ # DEDUPE
+ print(' ADDED NEIGHBORS, DEDUPING...')
+ rlist = deDupe(ll + nlist)
+ qlist = []
+
+ # RELOCATE GRID W/ RESPECT GSCALE AND BOLT ORIGIN
+ # !!!NEED TO ADD IN OBJ ROT/SCALE HERE SOMEHOW...
+ od = Vector(
+ ((ob.location[0] - orig[0]) / gs,
+ (ob.location[1] - orig[1]) / gs,
+ (ob.location[2] - orig[2]) / gs)
+ )
+ for r in rlist:
+ qlist.append((r[0] + int(od[0]), r[1] + int(od[1]), r[2] + int(od[2])))
+
+ return qlist
+
+
+def fakeGroundChargePlane(z, charge):
+ eCL = []
+ xy = abs(z) / 2
+ eCL += [(0, 0, z, charge)]
+ eCL += [(xy, 0, z, charge)]
+ eCL += [(0, xy, z, charge)]
+ eCL += [(-xy, 0, z, charge)]
+ eCL += [(0, -xy, z, charge)]
+ return eCL
+
+
+def addCharges(ll, charge):
+ # IN: ll - [(x,y,z), (x,y,z)], charge - w
+ # OUT clist - [(x,y,z,w), (x,y,z,w)]
+ clist = []
+ for l in ll:
+ clist.append((l[0], l[1], l[2], charge))
+ return clist
+
+
+# ALGORITHM FXNS #
+# FROM FSLG #
+
+def getGrowthProbability_KEEPFORREFERENCE(uN, aList):
+ # IN: uN -USER TERM, cList -CANDIDATE SITES, oList -CANDIDATE SITE CHARGES
+ # OUT: LIST OF [(XYZ), POT, PROB]
+ cList = splitList(aList, 0)
+ oList = splitList(aList, 1)
+ Omin, Omax = getLowHigh(oList)
+ if Omin == Omax:
+ Omax += notZero
+ Omin -= notZero
+ PdL = []
+ E = 0
+ E = notZero # DIVISOR FOR (FSLG - Eqn. 12)
+ for o in oList:
+ Uj = (o - Omin) / (Omax - Omin) # (FSLG - Eqn. 13)
+ E += pow(Uj, uN)
+ for oi in range(len(oList)):
+ o = oList[oi]
+ Ui = (o - Omin) / (Omax - Omin)
+ Pd = (pow(Ui, uN)) / E # (FSLG - Eqn. 12)
+ PdINT = Pd * 100
+ PdL.append(Pd)
+ return PdL
+
+
+# WORK IN PROGRESS, TRYING TO SPEED THESE UP
+def fslg_e13(x, min, max, u):
+ return pow((x - min) / (max - min), u)
+
+
+def addit(x, y):
+ return x + y
+
+
+def fslg_e12(x, min, max, u, e):
+ return (fslg_e13(x, min, max, u) / e) * 100
+
+
+def getGrowthProbability(uN, aList):
+ # IN: uN -USER TERM, cList -CANDIDATE SITES, oList -CANDIDATE SITE CHARGES
+ # OUT: LIST OF PROB
+ cList = splitList(aList, 0)
+ oList = splitList(aList, 1)
+ Omin, Omax = getLowHigh(oList)
+ if Omin == Omax:
+ Omax += notZero
+ Omin -= notZero
+ PdL = []
+ E = notZero
+ minL = [Omin for q in range(len(oList))]
+ maxL = [Omax for q in range(len(oList))]
+ uNL = [uN for q in range(len(oList))]
+ E = sum(map(fslg_e13, oList, minL, maxL, uNL))
+ EL = [E for q in range(len(oList))]
+ mp = map(fslg_e12, oList, minL, maxL, uNL, EL)
+ for m in mp:
+ PdL.append(m)
+ return PdL
+
+
+def updatePointCharges(p, cList, eList=[]):
+ # IN: pNew -NEW GROWTH CELL
+ # cList -OLD CANDIDATE SITES, eList -SAME
+ # OUT: LIST OF NEW CHARGE AT CANDIDATE SITES
+ r1 = 1 / 2 # (FSLG - Eqn. 10)
+ nOiL = []
+ for oi in range(len(cList)):
+ o = cList[oi][1]
+ c = cList[oi][0]
+ iOe = 0
+ rit = dist(c[0], c[1], c[2], p[0], p[1], p[2])
+ iOe += (1 - (r1 / rit))
+ Oit = o + iOe
+ nOiL.append((c, Oit))
+ return nOiL
+
+
+def initialPointCharges(pList, cList, eList=[]):
+ # IN: p -CHARGED CELL (XYZ), cList -CANDIDATE SITES (XYZ, POT, PROB)
+ # OUT: cList -WITH POTENTIAL CALCULATED
+ r1 = 1 / 2 # (FSLG - Eqn. 10)
+ npList = []
+ for p in pList:
+ npList.append(((p[0], p[1], p[2]), 1.0))
+ for e in eList:
+ npList.append(((e[0], e[1], e[2]), e[3]))
+ OiL = []
+ for i in cList:
+ Oi = 0
+ for j in npList:
+ if i != j[0]:
+ rij = dist(i[0], i[1], i[2], j[0][0], j[0][1], j[0][2])
+ Oi += (1 - (r1 / rij)) * j[1] # CHARGE INFLUENCE
+ OiL.append(((i[0], i[1], i[2]), Oi))
+ return OiL
+
+
+def getCandidateSites(aList, iList=[]):
+ # IN: aList -(X,Y,Z) OF CHARGED CELL SITES, iList -insulator sites
+ # OUT: CANDIDATE LIST OF GROWTH SITES [(X,Y,Z)]
+ tt1 = time.clock()
+ cList = []
+ for c in aList:
+ tempList = getStencil3D_26(c[0], c[1], c[2])
+ for t in tempList:
+ if t not in aList and t not in iList:
+ cList.append(t)
+ ncList = deDupe(cList)
+ tt2 = time.clock()
+
+ return ncList
+
+
+# SETUP FXNS #
+
+def setupObjects():
+ oOB = bpy.data.objects.new('ELorigin', None)
+ oOB.location = ((0, 0, 10))
+ bpy.context.scene.objects.link(oOB)
+
+ gOB = bpy.data.objects.new('ELground', None)
+ gOB.empty_draw_type = 'ARROWS'
+ bpy.context.scene.objects.link(gOB)
+
+ cME = makeMeshCube(1)
+ cOB = bpy.data.objects.new('ELcloud', cME)
+ cOB.location = ((-2, 8, 12))
+ cOB.hide_render = True
+ bpy.context.scene.objects.link(cOB)
+
+ iME = makeMeshCube(1)
+ for v in iME.vertices:
+ xyl = 6.5
+ zl = .5
+ v.co[0] = v.co[0] * xyl
+ v.co[1] = v.co[1] * xyl
+ v.co[2] = v.co[2] * zl
+ iOB = bpy.data.objects.new('ELinsulator', iME)
+ iOB.location = ((0, 0, 5))
+ iOB.hide_render = True
+ bpy.context.scene.objects.link(iOB)
+
+ try:
+ winmgr.OOB = 'ELorigin'
+ winmgr.GOB = 'ELground'
+ winmgr.COB = 'ELcloud'
+ winmgr.IOB = 'ELinsulator'
+ except:
+ pass
+
+
+def checkSettings():
+ check = True
+ if winmgr.OOB == "":
+ print('ERROR: NO ORIGIN OBJECT SELECTED')
+ check = False
+ if winmgr.GROUNDBOOL and winmgr.GOB == "":
+ print('ERROR: NO GROUND OBJECT SELECTED')
+ check = False
+ if winmgr.CLOUDBOOL and winmgr.COB == "":
+ print('ERROR: NO CLOUD OBJECT SELECTED')
+ check = False
+ if winmgr.IBOOL and winmgr.IOB == "":
+ print('ERROR: NO INSULATOR OBJECT SELECTED')
+ check = False
+ # should make a popup here
+ return check
+
+
+# MAIN #
+
+def FSLG():
+ # FAST SIMULATION OF LAPLACIAN GROWTH #
+ print('\n<<<<<<------GO GO GADGET: FAST SIMULATION OF LAPLACIAN GROWTH!')
+ tc1 = time.clock()
+ TSTEPS = winmgr.TSTEPS
+
+ obORIGIN = bpy.context.scene.objects[winmgr.OOB]
+ obGROUND = bpy.context.scene.objects[winmgr.GOB]
+ winmgr.ORIGIN = obORIGIN.location
+ winmgr.GROUNDZ = int((obGROUND.location[2] - winmgr.ORIGIN[2]) / winmgr.GSCALE)
+
+ # 1) INSERT INTIAL CHARGE(S) POINT (USES VERTS IF MESH)
+ cgrid = [(0, 0, 0)]
+ if obORIGIN.type == 'MESH':
+ print("<<<<<<------ORIGIN OBJECT IS MESH, 'VOXELIZING' INTIAL CHARGES FROM VERTS")
+ cgrid = voxelByVertex(obORIGIN, winmgr.GSCALE)
+ if winmgr.VMMESH:
+ print("<<<<<<------CANNOT CLASSIFY STROKE FROM VERT ORIGINS YET, NO MULTI-MESH OUTPUT")
+ winmgr.VMMESH = False
+ winmgr.VSMESH = True
+
+ # GROUND CHARGE CELL / INSULATOR LISTS (eChargeList/icList)
+ eChargeList = []
+ icList = []
+ if winmgr.GROUNDBOOL:
+ eChargeList = fakeGroundChargePlane(winmgr.GROUNDZ, winmgr.GROUNDC)
+ if winmgr.CLOUDBOOL:
+ print("<<<<<<------'VOXELIZING' CLOUD OBJECT (COULD TAKE SOME TIME)")
+ obCLOUD = bpy.context.scene.objects[winmgr.COB]
+ eChargeListQ = voxelByRays(obCLOUD, winmgr.ORIGIN, winmgr.GSCALE)
+ eChargeList = addCharges(eChargeListQ, winmgr.CLOUDC)
+ print('<<<<<<------CLOUD OBJECT CELL COUNT = ', len(eChargeList))
+ if winmgr.IBOOL:
+ print("<<<<<<------'VOXELIZING' INSULATOR OBJECT (COULD TAKE SOME TIME)")
+ obINSULATOR = bpy.context.scene.objects[winmgr.IOB]
+ icList = voxelByRays(obINSULATOR, winmgr.ORIGIN, winmgr.GSCALE)
+ print('<<<<<<------INSULATOR OBJECT CELL COUNT = ', len(icList))
+
+ # 2) LOCATE CANDIDATE SITES AROUND CHARGE
+ cSites = getCandidateSites(cgrid, icList)
+
+ # 3) CALC POTENTIAL AT EACH SITE (Eqn. 10)
+ cSites = initialPointCharges(cgrid, cSites, eChargeList)
+
+ ts = 1
+ while ts <= TSTEPS:
+ # 1) SELECT NEW GROWTH SITE (Eqn. 12)
+ # GET PROBABILITIES AT CANDIDATE SITES
+ gProbs = getGrowthProbability(winmgr.BIGVAR, cSites)
+ # CHOOSE NEW GROWTH SITE BASED ON PROBABILITIES
+ gSitei = weightedRandomChoice(gProbs)
+ gsite = cSites[gSitei][0]
+
+ # 2) ADD NEW POINT CHARGE AT GROWTH SITE
+ # ADD NEW GROWTH CELL TO GRID
+ cgrid.append(gsite)
+ # REMOVE NEW GROWTH CELL FROM CANDIDATE SITES
+ cSites.remove(cSites[gSitei])
+
+ # 3) UPDATE POTENTIAL AT CANDIDATE SITES (Eqn. 11)
+ cSites = updatePointCharges(gsite, cSites, eChargeList)
+
+ # 4) ADD NEW CANDIDATES SURROUNDING GROWTH SITE
+ # GET CANDIDATE 'STENCIL'
+ ncSitesT = getCandidateSites([gsite], icList)
+ # REMOVE CANDIDATES ALREADY IN CANDIDATE LIST OR CHARGE GRID
+ ncSites = []
+ cSplit = splitList(cSites, 0)
+ for cn in ncSitesT:
+ if cn not in cSplit and cn not in cgrid:
+ ncSites.append((cn, 0))
+
+ # 5) CALC POTENTIAL AT NEW CANDIDATE SITES (Eqn. 10)
+ ncSplit = splitList(ncSites, 0)
+ ncSites = initialPointCharges(cgrid, ncSplit, eChargeList)
+
+ # ADD NEW CANDIDATE SITES TO CANDIDATE LIST
+ for ncs in ncSites:
+ cSites.append(ncs)
+
+ # ITERATION COMPLETE
+ istr1 = ':::T-STEP: ' + str(ts) + '/' + str(TSTEPS)
+ istr12 = ' | GROUNDZ: ' + str(winmgr.GROUNDZ) + ' | '
+ istr2 = 'CANDS: ' + str(len(cSites)) + ' | '
+ istr3 = 'GSITE: ' + str(gsite)
+ print(istr1 + istr12 + istr2 + istr3)
+ ts += 1
+
+ # EARLY TERMINATION FOR GROUND/CLOUD STRIKE
+ if winmgr.GROUNDBOOL:
+ if gsite[2] == winmgr.GROUNDZ:
+ ts = TSTEPS + 1
+ print('<<<<<<------EARLY TERMINATION DUE TO GROUNDSTRIKE')
+ continue
+ if winmgr.CLOUDBOOL:
+ if gsite in splitListCo(eChargeList):
+ ts = TSTEPS + 1
+ print('<<<<<<------EARLY TERMINATION DUE TO CLOUDSTRIKE')
+ continue
+
+ tc2 = time.clock()
+ tcRUN = tc2 - tc1
+ print('<<<<<<------LAPLACIAN GROWTH LOOP COMPLETED: ' + str(len(cgrid)) + ' / ' + str(tcRUN)[0:5] + ' SECONDS')
+ print('<<<<<<------VISUALIZING DATA')
+
+ reportSTRING = getReportString(tcRUN)
+ # VISUALIZE ARRAY
+ visualizeArray(
+ cgrid, obORIGIN, winmgr.GSCALE,
+ winmgr.VMMESH, winmgr.VSMESH,
+ winmgr.VCUBE, winmgr.VVOX, reportSTRING
+ )
+ print('<<<<<<------COMPLETE')
+
+
+# GUI #
+
+# NOT IN UI
+bpy.types.WindowManager.ORIGIN = bpy.props.FloatVectorProperty(name="origin charge")
+bpy.types.WindowManager.GROUNDZ = bpy.props.IntProperty(name="ground Z coordinate")
+bpy.types.WindowManager.HORDER = bpy.props.IntProperty(name="secondary paths orders")
+# IN UI
+bpy.types.WindowManager.TSTEPS = bpy.props.IntProperty(
+ name="iterations",
+ description="number of cells to create, will end early if hits ground plane or cloud")
+bpy.types.WindowManager.GSCALE = bpy.props.FloatProperty(
+ name="grid unit size",
+ description="scale of cells, .25 = 4 cells per blenderUnit")
+bpy.types.WindowManager.BIGVAR = bpy.props.FloatProperty(
+ name="straightness",
+ description="straightness/branchiness of bolt, <2 is mush, >12 is staight line, 6.3 is good")
+bpy.types.WindowManager.GROUNDBOOL = bpy.props.BoolProperty(
+ name="use ground object", description="use ground plane or not")
+bpy.types.WindowManager.GROUNDC = bpy.props.IntProperty(
+ name="ground charge", description="charge of ground plane")
+bpy.types.WindowManager.CLOUDBOOL = bpy.props.BoolProperty(
+ name="use cloud object",
+ description="use cloud obj, attracts and terminates like ground but "
+ "any obj instead of z plane, can slow down loop if obj is large, overrides ground")
+bpy.types.WindowManager.CLOUDC = bpy.props.IntProperty(
+ name="cloud charge",
+ description="charge of a cell in cloud object (so total charge also depends on obj size)")
+
+bpy.types.WindowManager.VMMESH = bpy.props.BoolProperty(
+ name="multi mesh",
+ description="output to multi-meshes for different materials on main/sec/side branches")
+bpy.types.WindowManager.VSMESH = bpy.props.BoolProperty(
+ name="single mesh",
+ description="output to single mesh for using build modifier and particles for effects")
+bpy.types.WindowManager.VCUBE = bpy.props.BoolProperty(
+ name="cubes", description="CTRL-J after run to JOIN, outputs a bunch of cube objest, mostly for testing")
+bpy.types.WindowManager.VVOX = bpy.props.BoolProperty(
+ name="voxel (experimental)",
+ description="output to a voxel file to bpy.data.filepath\FSLGvoxels.raw - doesn't work well right now")
+bpy.types.WindowManager.IBOOL = bpy.props.BoolProperty(
+ name="use insulator object", description="use insulator mesh object to prevent growth of bolt in areas")
+bpy.types.WindowManager.OOB = bpy.props.StringProperty(
+ description="origin of bolt, can be an Empty, if obj is mesh will use all verts as charges")
+bpy.types.WindowManager.GOB = bpy.props.StringProperty(
+ description="object to use as ground plane, uses z coord only")
+bpy.types.WindowManager.COB = bpy.props.StringProperty(
+ description="object to use as cloud, best to use a cube")
+bpy.types.WindowManager.IOB = bpy.props.StringProperty(
+ description="object to use as insulator, 'voxelized' before generating bolt, can be slow")
+
+# DEFAULT USER SETTINGS
+winmgr.TSTEPS = 350
+winmgr.HORDER = 1
+winmgr.GSCALE = 0.12
+winmgr.BIGVAR = 6.3
+winmgr.GROUNDBOOL = True
+winmgr.GROUNDC = -250
+winmgr.CLOUDBOOL = False
+winmgr.CLOUDC = -1
+winmgr.VMMESH = True
+winmgr.VSMESH = False
+winmgr.VCUBE = False
+winmgr.VVOX = False
+winmgr.IBOOL = False
+try:
+ winmgr.OOB = "ELorigin"
+ winmgr.GOB = "ELground"
+ winmgr.COB = "ELcloud"
+ winmgr.IOB = "ELinsulator"
+except:
+ pass
+
+# TESTING USER SETTINGS
+if False:
+ winmgr.TSTEPS = 40
+ winmgr.GROUNDBOOL = True
+ winmgr.CLOUDBOOL = True
+ winmgr.IBOOL = True
+
+
+class runFSLGLoopOperator(bpy.types.Operator):
+ '''By The Mighty Hammer Of Thor!!!'''
+ bl_idname = "object.runfslg_operator"
+ bl_label = "run FSLG Loop Operator"
+
+ def execute(self, context):
+ if checkSettings():
+ FSLG()
+ else:
+ pass
+ return {'FINISHED'}
+
+
+class setupObjectsOperator(bpy.types.Operator):
+ '''create origin/ground/cloud/insulator objects'''
+ bl_idname = "object.setup_objects_operator"
+ bl_label = "Setup Objects Operator"
+
+ def execute(self, context):
+ setupObjects()
+ return {'FINISHED'}
+
+
+class OBJECT_PT_fslg(bpy.types.Panel):
+ bl_label = "Laplacian Lightning"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_context = "objectmode"
+ bl_category = "Create"
+ bl_options = {'DEFAULT_CLOSED'}
+
+ def draw(self, context):
+ layout = self.layout
+ colR = layout.column()
+ colR.label('-for progress open console-')
+ colR.label('Help > Toggle System Console')
+ colR.prop(winmgr, 'TSTEPS')
+ colR.prop(winmgr, 'GSCALE')
+ colR.prop(winmgr, 'BIGVAR')
+ colR.operator('object.setup_objects_operator', text='create setup objects')
+ colR.label('origin object')
+ colR.prop_search(winmgr, "OOB", context.scene, "objects")
+ colR.prop(winmgr, 'GROUNDBOOL')
+ colR.prop_search(winmgr, "GOB", context.scene, "objects")
+ colR.prop(winmgr, 'GROUNDC')
+ colR.prop(winmgr, 'CLOUDBOOL')
+ colR.prop_search(winmgr, "COB", context.scene, "objects")
+ colR.prop(winmgr, 'CLOUDC')
+ colR.prop(winmgr, 'IBOOL')
+ colR.prop_search(winmgr, "IOB", context.scene, "objects")
+ colR.operator('object.runfslg_operator', text='generate lightning')
+ colR.prop(winmgr, 'VMMESH')
+ colR.prop(winmgr, 'VSMESH')
+ colR.prop(winmgr, 'VCUBE')
+
+
+def getReportString(rtime):
+ rSTRING1 = 't:' + str(winmgr.TSTEPS) + ',sc:' + str(winmgr.GSCALE)[0:4] + ',uv:' + str(winmgr.BIGVAR)[0:4] + ','
+ rSTRING2 = 'ori:' + str(winmgr. ORIGIN[0]) + '/' + str(winmgr. ORIGIN[1]) + '/' + str(winmgr. ORIGIN[2]) + ','
+ rSTRING3 = 'gz:' + str(winmgr.GROUNDZ) + ',gc:' + str(winmgr.GROUNDC) + ',rtime:' + str(int(rtime))
+ return rSTRING1 + rSTRING2 + rSTRING3
+
+
+def addReportProp(ob, str):
+ bpy.types.Object.FSLG_REPORT = bpy.props.StringProperty(
+ name='fslg_report', default='')
+ ob.FSLG_REPORT = str
+
+
+def register():
+ bpy.utils.register_class(runFSLGLoopOperator)
+ bpy.utils.register_class(setupObjectsOperator)
+ bpy.utils.register_class(OBJECT_PT_fslg)
+
+
+def unregister():
+ bpy.utils.unregister_class(runFSLGLoopOperator)
+ bpy.utils.unregister_class(setupObjectsOperator)
+ bpy.utils.unregister_class(OBJECT_PT_fslg)
+
+
+if __name__ == "__main__":
+ # RUN FOR TESTING
+ # FSLG()
+
+ # UI
+ register()
+ pass
+
+
+# FXN BENCHMARKS #
+
+def BENCH():
+ print('\n\n\n--->BEGIN BENCHMARK')
+ bt0 = time.clock()
+ # MAKE A BIG LIST
+ tsize = 25
+ tlist = []
+ for x in range(tsize):
+ for y in range(tsize):
+ for z in range(tsize):
+ tlist.append((x, y, z))
+ tlist.append((x, y, z))
+
+ # FUNCTION TO TEST
+ bt1 = time.clock()
+ # print('LENS - ', len(tlist), len(ll))
+
+ bt2 = time.clock()
+ btRUNb = bt2 - bt1
+ btRUNa = bt1 - bt0
+ print('--->SETUP TIME : ', btRUNa)
+ print('--->BENCHMARK TIME: ', btRUNb)
+ print('--->GRIDSIZE: ', tsize, ' - ', tsize * tsize * tsize)
diff --git a/add_advanced_objects/object_mangle_tools.py b/add_advanced_objects/object_mangle_tools.py
new file mode 100644
index 00000000..81110ad3
--- /dev/null
+++ b/add_advanced_objects/object_mangle_tools.py
@@ -0,0 +1,211 @@
+# mangle_tools.py (c) 2011 Phil Cote (cotejrp1)
+#
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ***** END GPL LICENCE BLOCK *****
+
+bl_info = {
+ "name": "Mangle Tools",
+ "author": "Phil Cote",
+ "version": (0, 2),
+ "blender": (2, 71, 0),
+ "location": "View3D > Toolshelf > Tools Tab",
+ "description": "Set of tools to mangle curves, meshes, and shape keys",
+ "warning": "", # used for warning icon and text in addons panel
+ "wiki_url": "",
+ "tracker_url": "https://developer.blender.org/maniphest/task/edit/form/2/",
+ "category": "Object"}
+
+
+import bpy
+import random
+import time
+from math import pi
+import bmesh
+
+def move_coordinate(context, co, is_curve=False):
+ xyz_const = context.scene.constraint_vector
+ random.seed(time.time())
+ multiplier = 1
+
+ # For curves, we base the multiplier on the circumference formula.
+ # This helps make curve changes more noticable.
+ if is_curve:
+ multiplier = 2 * pi
+ random_mag = context.scene.random_magnitude
+ if xyz_const[0]:
+ co.x += .01 * random.randrange( -random_mag, random_mag ) * multiplier
+ if xyz_const[1]:
+ co.y += .01 * random.randrange( -random_mag, random_mag ) * multiplier
+ if xyz_const[2]:
+ co.z += .01 * random.randrange( -random_mag, random_mag ) * multiplier
+
+
+class MeshManglerOperator(bpy.types.Operator):
+ """Push vertices on the selected object around in random """ \
+ """directions to create a crumpled look"""
+ bl_idname = "ba.mesh_mangler"
+ bl_label = "Mangle Mesh"
+ bl_options = { "REGISTER", "UNDO" }
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.active_object
+ return ob != None and ob.type == 'MESH'
+
+ def execute(self, context):
+ mesh = context.active_object.data
+ bm = bmesh.new()
+ bm.from_mesh(mesh)
+ verts, faces = bm.verts, bm.faces
+ randomMag = context.scene.random_magnitude
+ random.seed( time.time() )
+
+ if mesh.shape_keys != None:
+ self.report({'INFO'}, "Cannot mangle mesh: Shape keys present")
+ return {'CANCELLED'}
+
+ for vert in verts:
+ xVal = .01 * random.randrange( -randomMag, randomMag )
+ yVal = .01 * random.randrange( -randomMag, randomMag)
+ zVal = .01 * random.randrange( -randomMag, randomMag )
+ vert.co.x = vert.co.x + xVal
+ vert.co.y = vert.co.y + yVal
+ vert.co.z = vert.co.z + zVal
+
+ bm.to_mesh(mesh)
+ mesh.update()
+ return {'FINISHED'}
+
+
+class AnimanglerOperator(bpy.types.Operator):
+ """Make a shape key and pushes the verts around on it """ \
+ """to set up for random pulsating animation"""
+ bl_idname = "ba.ani_mangler"
+ bl_label = "Mangle Shape Key"
+
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.active_object
+ return ob != None and ob.type in [ 'MESH', 'CURVE' ]
+
+ def execute(self, context):
+ scn = context.scene
+ mangleName = scn.mangle_name
+ ob = context.object
+ shapeKey = ob.shape_key_add( name=mangleName )
+ verts = shapeKey.data
+
+ for vert in verts:
+ move_coordinate(context, vert.co, is_curve=ob.type=='CURVE')
+
+ return {'FINISHED'}
+
+
+class CurveManglerOp(bpy.types.Operator):
+ """Mangle a curve to the degree the user specifies"""
+ bl_idname = "ba.curve_mangler"
+ bl_label = "Mangle Curve"
+ bl_options = { 'REGISTER', 'UNDO' }
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.active_object
+ return ob != None and ob.type == "CURVE"
+
+
+ def execute(self, context):
+
+ ob = context.active_object
+ if ob.data.shape_keys != None:
+ self.report({'INFO'}, "Cannot mangle curve. Shape keys present")
+ return {'CANCELLED'}
+ splines = context.object.data.splines
+
+ for spline in splines:
+ if spline.type == 'BEZIER':
+ points = spline.bezier_points
+ elif spline.type in {'POLY', 'NURBS'}:
+ points = spline.points
+
+ for point in points:
+ move_coordinate(context, point.co, is_curve=True)
+
+ return {'FINISHED'}
+
+
+class MangleToolsPanel(bpy.types.Panel):
+ bl_label = "Mangle Tools"
+ bl_space_type = "VIEW_3D"
+ bl_context = "objectmode"
+ bl_region_type="TOOLS"
+ bl_category = "Create"
+ bl_options = {'DEFAULT_CLOSED'}
+
+
+ def draw(self, context):
+ scn = context.scene
+ obj = context.object
+ if obj.type in ['MESH',]:
+ layout = self.layout
+ col = layout.column()
+ col.prop(scn, "constraint_vector")
+ col.prop(scn, "random_magnitude")
+ col.operator("ba.mesh_mangler")
+ col.separator()
+ col.prop(scn, "mangle_name")
+ col.operator("ba.ani_mangler")
+ else:
+ layout = self.layout
+ col = layout.column()
+ col.label("Please Select Mesh Object")
+
+IntProperty = bpy.props.IntProperty
+StringProperty = bpy.props.StringProperty
+BoolVectorProperty = bpy.props.BoolVectorProperty
+
+def register():
+ bpy.utils.register_class(AnimanglerOperator)
+ bpy.utils.register_class(MeshManglerOperator)
+ bpy.utils.register_class(CurveManglerOp)
+ bpy.utils.register_class(MangleToolsPanel)
+ scnType = bpy.types.Scene
+
+
+ scnType.constraint_vector = BoolVectorProperty(name="Mangle Constraint",
+ default=(True,True,True),
+ subtype='XYZ',
+ description="Constrains Mangle Direction")
+
+ scnType.random_magnitude = IntProperty( name = "Mangle Severity",
+ default = 5, min = 1, max = 30,
+ description = "Severity of mangling")
+
+ scnType.mangle_name = StringProperty(name="Shape Key Name",
+ default="mangle",
+ description="Name given for mangled shape keys")
+def unregister():
+ bpy.utils.unregister_class(AnimanglerOperator)
+ bpy.utils.unregister_class(MeshManglerOperator)
+ bpy.utils.unregister_class(MangleToolsPanel)
+ bpy.utils.unregister_class(CurveManglerOp)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/oscurart_chain_maker.py b/add_advanced_objects/oscurart_chain_maker.py
new file mode 100644
index 00000000..c336e44d
--- /dev/null
+++ b/add_advanced_objects/oscurart_chain_maker.py
@@ -0,0 +1,280 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# TODO: translate the comments into English
+
+bl_info = {
+ "name": "Oscurart Chain Maker",
+ "author": "Oscurart",
+ "version": (1, 1),
+ "blender": (2, 56, 0),
+ "location": "Add > Mesh > Oscurart Chain",
+ "description": "Create chain links from armatures",
+ "warning": "",
+ "wiki_url": "oscurart.blogspot.com",
+ "tracker_url": "",
+ "category": "Object"}
+
+
+import bpy
+from bpy.props import (
+ BoolProperty,
+ FloatProperty,
+ )
+from bpy.types import Operator
+
+
+def makeChain(self, context, mult, curverig):
+
+ if not context.active_object.type == 'ARMATURE':
+ self.report({'WARNING'}, "Active Object must be an Armature")
+ return False
+
+ bpy.ops.object.mode_set(mode='OBJECT')
+ VAR_SWITCH = abs(1)
+ ARMATURE = bpy.context.active_object
+
+ def creahuesocero(hueso):
+ # create data to link
+ mesh = bpy.data.meshes.new("objectData" + str(hueso.name))
+ object = bpy.data.objects.new("EslabonCero" + str(hueso.name), mesh)
+ mesh.from_pydata(
+ [(-0.04986128956079483, -0.6918092370033264, -0.17846597731113434),
+ (-0.04986128956079483, -0.6918091773986816, 0.17846640944480896),
+ (-0.049861326813697815, -0.154555082321167, 0.17846627533435822),
+ (-0.049861326813697815, -0.15455523133277893, -0.17846614122390747),
+ (-0.04986133798956871, -0.03475356101989746, 0.25805795192718506),
+ (-0.04986133798956871, -0.03475397825241089, -0.25805795192718506),
+ (-0.049861278384923935, -0.8116106986999512, -0.2580576539039612),
+ (-0.049861278384923935, -0.8116104602813721, 0.25805822014808655),
+ (-0.04986128211021423, -0.7692053318023682, 2.6668965347198537e-07),
+ (-0.04986127093434334, -0.923523485660553, 2.7834033744511544e-07),
+ (-0.04986133426427841, -0.0771591067314148, 3.5627678585115063e-08),
+ (-0.04986134544014931, 0.0771591067314148, -3.5627678585115063e-08),
+ (0.04986133798956871, -0.03475397825241089, -0.25805795192718506),
+ (0.04986133053898811, 0.0771591067314148, -3.5627678585115063e-08),
+ (0.04986133798956871, -0.03475356101989746, 0.25805795192718506),
+ (0.04986134544014931, -0.15455523133277893, -0.17846614122390747),
+ (0.04986134544014931, -0.0771591067314148, 3.5627678585115063e-08),
+ (0.04986134544014931, -0.154555082321167, 0.17846627533435822),
+ (0.049861397594213486, -0.8116106986999512, -0.2580576539039612),
+ (0.04986140504479408, -0.923523485660553, 2.7834033744511544e-07),
+ (0.049861397594213486, -0.8116104602813721, 0.25805822014808655),
+ (0.04986139014363289, -0.6918091773986816, 0.17846640944480896),
+ (0.04986139014363289, -0.7692053318023682, 2.6668965347198537e-07),
+ (0.04986139014363289, -0.6918092370033264, -0.17846597731113434)],
+ [(1, 2), (0, 3), (3, 5), (2, 4), (0, 6), (5, 6), (1, 7), (4, 7), (0, 8), (1, 8),
+ (7, 9), (6, 9), (8, 9), (2, 10), (3, 10), (4, 11), (5, 11), (10, 11), (5, 12),
+ (12, 13), (11, 13), (13, 14), (4, 14), (10, 16), (15, 16), (3, 15), (2, 17),
+ (16, 17), (9, 19), (18, 19), (6, 18), (7, 20), (19, 20), (8, 22), (21, 22),
+ (1, 21), (0, 23), (22, 23), (14, 20), (12, 18), (15, 23), (17, 21), (12, 15),
+ (13, 16), (14, 17), (20, 21), (19, 22), (18, 23)],
+ [(6, 0, 3, 5), (1, 7, 4, 2), (0, 6, 9, 8), (8, 9, 7, 1), (2, 4, 11, 10), (10, 11, 5, 3),
+ (11, 13, 12, 5), (4, 14, 13, 11), (3, 15, 16, 10), (10, 16, 17, 2), (6, 18, 19, 9),
+ (9, 19, 20, 7), (1, 21, 22, 8), (23, 0, 8, 22), (7, 20, 14, 4), (5, 12, 18, 6),
+ (0, 23, 15, 3), (2, 17, 21, 1), (16, 15, 12, 13), (17, 16, 13, 14), (22, 21, 20, 19),
+ (23, 22, 19, 18), (21, 17, 14, 20), (15, 23, 18, 12)]
+ )
+ mesh.validate()
+ bpy.context.scene.objects.link(object)
+ # scale to the bone
+ bpy.data.objects['EslabonCero' + str(hueso.name)].scale = (hueso.length * mult,
+ hueso.length * mult,
+ hueso.length * mult)
+ # Parent Objects
+ bpy.data.objects['EslabonCero' + str(hueso.name)].parent = ARMATURE
+ bpy.data.objects['EslabonCero' + str(hueso.name)].parent_type = 'BONE'
+ bpy.data.objects['EslabonCero' + str(hueso.name)].parent_bone = hueso.name
+
+ def creahuesonoventa(hueso):
+ # create data to link
+ mesh = bpy.data.meshes.new("objectData" + str(hueso.name))
+ object = bpy.data.objects.new("EslabonNov" + str(hueso.name), mesh)
+ mesh.from_pydata(
+ [(0.1784660965204239, -0.6918091773986816, -0.049861203879117966),
+ (-0.1784662902355194, -0.6918091773986816, -0.04986126348376274),
+ (-0.17846627533435822, -0.1545550525188446, -0.04986134544014931),
+ (0.17846617102622986, -0.15455520153045654, -0.04986128583550453),
+ (-0.25805795192718506, -0.03475359082221985, -0.049861375242471695),
+ (0.25805795192718506, -0.034753888845443726, -0.04986129328608513),
+ (0.2580578327178955, -0.8116105794906616, -0.04986117407679558),
+ (-0.2580580413341522, -0.8116105198860168, -0.049861256033182144),
+ (-9.672299938756623e-08, -0.7692052721977234, -0.04986122250556946),
+ (-8.99775329799013e-08, -0.923523485660553, -0.04986120015382767),
+ (-7.764004550381287e-09, -0.07715904712677002, -0.049861326813697815),
+ (4.509517737005808e-08, 0.0771591067314148, -0.049861349165439606),
+ (0.25805795192718506, -0.034753888845443726, 0.049861375242471695),
+ (-2.2038317837314025e-08, 0.0771591067314148, 0.049861326813697815),
+ (-0.25805795192718506, -0.03475359082221985, 0.04986129328608513),
+ (0.17846617102622986, -0.15455520153045654, 0.04986138269305229),
+ (-1.529285498236277e-08, -0.07715907692909241, 0.049861352890729904),
+ (-0.17846627533435822, -0.1545550525188446, 0.049861323088407516),
+ (0.2580578029155731, -0.8116105794906616, 0.049861494451761246),
+ (-1.5711103173998708e-07, -0.923523485660553, 0.04986147582530975),
+ (-0.2580580711364746, -0.8116105198860168, 0.04986141249537468),
+ (-0.1784663051366806, -0.6918091773986816, 0.049861419945955276),
+ (-1.340541757599567e-07, -0.7692052721977234, 0.049861449748277664),
+ (0.1784660816192627, -0.6918091773986816, 0.04986146464943886)],
+ [(1, 2), (0, 3), (3, 5), (2, 4), (0, 6), (5, 6), (1, 7), (4, 7), (0, 8),
+ (1, 8), (7, 9), (6, 9), (8, 9), (2, 10), (3, 10), (4, 11), (5, 11), (10, 11),
+ (5, 12), (12, 13), (11, 13), (13, 14), (4, 14), (10, 16), (15, 16), (3, 15),
+ (2, 17), (16, 17), (9, 19), (18, 19), (6, 18), (7, 20), (19, 20), (8, 22),
+ (21, 22), (1, 21), (0, 23), (22, 23), (14, 20), (12, 18), (15, 23), (17, 21),
+ (12, 15), (13, 16), (14, 17), (20, 21), (19, 22), (18, 23)],
+ [(6, 0, 3, 5), (1, 7, 4, 2), (0, 6, 9, 8), (8, 9, 7, 1), (2, 4, 11, 10),
+ (10, 11, 5, 3), (11, 13, 12, 5), (4, 14, 13, 11), (3, 15, 16, 10), (10, 16, 17, 2),
+ (6, 18, 19, 9), (9, 19, 20, 7), (1, 21, 22, 8), (23, 0, 8, 22), (7, 20, 14, 4),
+ (5, 12, 18, 6), (0, 23, 15, 3), (2, 17, 21, 1), (16, 15, 12, 13), (17, 16, 13, 14),
+ (22, 21, 20, 19), (23, 22, 19, 18), (21, 17, 14, 20), (15, 23, 18, 12)]
+ )
+ mesh.validate()
+ bpy.context.scene.objects.link(object)
+ # scale to the bone
+ bpy.data.objects['EslabonNov' + str(hueso.name)].scale = (hueso.length * mult,
+ hueso.length * mult,
+ hueso.length * mult)
+ # Parent objects
+ bpy.data.objects['EslabonNov' + str(hueso.name)].parent = ARMATURE
+ bpy.data.objects['EslabonNov' + str(hueso.name)].parent_type = 'BONE'
+ bpy.data.objects['EslabonNov' + str(hueso.name)].parent_bone = hueso.name
+
+ for hueso in bpy.context.active_object.pose.bones:
+ if VAR_SWITCH == 1:
+ creahuesocero(hueso)
+ else:
+ creahuesonoventa(hueso)
+ if VAR_SWITCH == 1:
+ VAR_SWITCH = 0
+ else:
+ VAR_SWITCH = 1
+
+ # if curve rig is activated
+ if curverig is True:
+ # variables
+ LISTA_POINTC = []
+ ACTARM = bpy.context.active_object
+
+ # create data and link the object to the scene
+ crv = bpy.data.curves.new("CurvaCable", "CURVE")
+ obCable = bpy.data.objects.new("Cable", crv)
+ bpy.context.scene.objects.link(obCable)
+
+ # set the attributes
+ crv.dimensions = "3D"
+ crv.resolution_u = 10
+ crv.resolution_v = 10
+ crv.twist_mode = "MINIMUM"
+
+ # create the list of tail and head coordinates
+ LISTA_POINTC.append((
+ ACTARM.data.bones[0].head_local[0],
+ ACTARM.data.bones[0].head_local[1],
+ ACTARM.data.bones[0].head_local[2],
+ 1
+ ))
+
+ for hueso in ACTARM.data.bones:
+ LISTA_POINTC.append((
+ hueso.tail_local[0],
+ hueso.tail_local[1],
+ hueso.tail_local[2],
+ 1
+ ))
+
+ # create the Spline
+ spline = crv.splines.new("NURBS")
+ lencoord = len(LISTA_POINTC)
+ rango = range(lencoord)
+ spline.points.add(lencoord - 1)
+
+ for punto in rango:
+ spline.points[punto].co = LISTA_POINTC[punto]
+
+ # set the endpoint
+ bpy.data.objects['Cable'].data.splines[0].use_endpoint_u = True
+ # select the curve
+ bpy.ops.object.select_all(action='DESELECT')
+ bpy.data.objects['Cable'].select = 1
+ bpy.context.scene.objects.active = bpy.data.objects['Cable']
+ # switch to Edit mode
+ bpy.ops.object.mode_set(mode='EDIT')
+
+ # create hooks
+ POINTSTEP = 0
+ for POINT in bpy.data.objects['Cable'].data.splines[0].points:
+ bpy.ops.curve.select_all(action="DESELECT")
+ bpy.data.objects['Cable'].data.splines[0].points[POINTSTEP].select = 1
+ bpy.ops.object.hook_add_newob()
+ POINTSTEP += 1
+
+ # Objects selection step
+ bpy.ops.object.mode_set(mode='OBJECT')
+ bpy.ops.object.select_all(action='DESELECT')
+ ACTARM.select = 1
+ bpy.context.scene.objects.active = bpy.data.objects['Armature']
+ bpy.ops.object.mode_set(mode='POSE')
+ bpy.ops.pose.select_all(action='DESELECT')
+ ACTARM.data.bones[-1].select = 1
+ ACTARM.data.bones.active = ACTARM.data.bones[-1]
+
+ # set IK Spline
+ bpy.ops.pose.constraint_add_with_targets(type='SPLINE_IK')
+ ACTARM.pose.bones[-1].constraints['Spline IK'].target = bpy.data.objects['Cable']
+ ACTARM.pose.bones[-1].constraints['Spline IK'].chain_count = 100
+ bpy.context.active_object.pose.bones[-1].constraints['Spline IK'].use_y_stretch = False
+ # return to Object mode
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+
+class MESH_OT_primitive_oscurart_chain_add(Operator):
+ bl_idname = "mesh.primitive_oscurart_chain_add"
+ bl_label = "Chain to Bones"
+ bl_description = ("Add Chain Parented to an Existing Armature\n"
+ "The Active/Last Selected Object must be an Armature")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ curverig = BoolProperty(
+ name="Curve Rig",
+ default=False
+ )
+ multiplier = FloatProperty(
+ name="Scale",
+ default=1,
+ min=0.01, max=100.0
+ )
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.active_object
+ return (obj is not None and obj.type == "ARMATURE")
+
+ def execute(self, context):
+ makeChain(self, context, self.multiplier, self.curverig)
+ return {'FINISHED'}
+
+
+def register():
+ bpy.utils.register_class(MESH_OT_primitive_oscurart_chain_add)
+
+
+def unregister():
+ bpy.utils.unregister_class(MESH_OT_primitive_oscurart_chain_add)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/pixelate_3d.py b/add_advanced_objects/pixelate_3d.py
new file mode 100644
index 00000000..90e129da
--- /dev/null
+++ b/add_advanced_objects/pixelate_3d.py
@@ -0,0 +1,104 @@
+#######################################################
+# very simple 'pixelization' or 'voxelization' engine #
+#######################################################
+
+bl_info = {
+ "name": "3D Pix",
+ "author": "liero",
+ "version": (0, 5, 1),
+ "blender": (2, 74, 0),
+ "location": "View3D > Tool Shelf",
+ "description": "Creates a 3d pixelated version of the object.",
+ "category": "Object"}
+
+import bpy
+import mathutils
+from mathutils import Vector
+
+bpy.types.WindowManager.size = bpy.props.FloatProperty(name='Size', min=.05, max=5, default=.25, description='Size of the cube / grid')
+bpy.types.WindowManager.gap = bpy.props.IntProperty(name='Gap', min=0, max=90, default=10, subtype='PERCENTAGE', description='Separation - percent of size')
+bpy.types.WindowManager.smooth = bpy.props.FloatProperty(name='Smooth', min=0, max=1, default=.0, description='Smooth factor when subdividing mesh')
+
+
+def pix(obj):
+ sce = bpy.context.scene
+ wm = bpy.context.window_manager
+ obj.hide = obj.hide_render = True
+ mes = obj.to_mesh(sce, True, 'RENDER')
+ mes.transform(obj.matrix_world)
+ dup = bpy.data.objects.new('dup', mes)
+ sce.objects.link(dup)
+ dup.dupli_type = 'VERTS'
+ sce.objects.active = dup
+ bpy.ops.object.mode_set()
+ ver = mes.vertices
+
+ for i in range(250):
+ fin = True
+ for i in dup.data.edges:
+ d = ver[i.vertices[0]].co - ver[i.vertices[1]].co
+ if d.length > wm.size:
+ ver[i.vertices[0]].select = True
+ ver[i.vertices[1]].select = True
+ fin = False
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.subdivide(number_cuts=1, smoothness=wm.smooth)
+ bpy.ops.mesh.select_all(action='DESELECT')
+ bpy.ops.object.editmode_toggle()
+ if fin:
+ break
+
+ for i in ver:
+ for n in range(3):
+ i.co[n] -= (.001 + i.co[n]) % wm.size
+
+ bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.mesh.remove_doubles(threshold=0.0001)
+ bpy.ops.mesh.delete(type='EDGE_FACE')
+ bpy.ops.object.mode_set()
+ sca = wm.size * (100 - wm.gap) * .005
+ bpy.ops.mesh.primitive_cube_add(layers=[True] + [False] * 19)
+ bpy.ops.transform.resize(value=[sca] * 3)
+ bpy.context.scene.objects.active = dup
+ bpy.ops.object.parent_set(type='OBJECT')
+
+
+class Pixelate(bpy.types.Operator):
+ bl_idname = 'object.pixelate'
+ bl_label = 'Pixelate Object'
+ bl_description = 'Create a 3d pixelated version of the object.'
+ bl_options = {'REGISTER', 'UNDO'}
+
+ @classmethod
+ def poll(cls, context):
+ return (context.active_object and context.active_object.type == 'MESH' and context.mode == 'OBJECT')
+
+ def draw(self, context):
+ layout = self.layout
+
+ column = layout.column(align=True)
+ column.prop(context.window_manager, "size")
+ column.prop(context.window_manager, "gap")
+ layout.prop(context.window_manager, "smooth")
+
+ def execute(self, context):
+ objeto = bpy.context.object
+ pix(objeto)
+ return {'FINISHED'}
+
+classes = (
+ Pixelate,
+)
+
+def register():
+ for cls in classes:
+ bpy.utils.register_class(cls)
+
+
+def unregister():
+ for cls in classes:
+ bpy.utils.unregister_class(cls)
+
+if __name__ == '__main__':
+ register()
diff --git a/add_advanced_objects/random_box_structure.py b/add_advanced_objects/random_box_structure.py
new file mode 100644
index 00000000..5440b8b0
--- /dev/null
+++ b/add_advanced_objects/random_box_structure.py
@@ -0,0 +1,193 @@
+bl_info = {
+ "name": "Add Random Box Structure",
+ "author": "Dannyboy",
+ "version": (1, 0),
+ "location": "View3D > Add > Make Box Structure",
+ "description": "Fill selected box shaped meshes with randomly sized cubes.",
+ "warning": "",
+ "wiki_url": "",
+ "tracker_url": "dannyboypython.blogspot.com",
+ "category": "Object"}
+
+import bpy
+import random
+from bpy.types import Operator
+from bpy.props import (
+ BoolProperty,
+ FloatProperty,
+ FloatVectorProperty,
+ IntProperty,
+ )
+
+
+class makestructure(Operator):
+ bl_idname = "object.make_structure"
+ bl_label = "Add Random Box Structure"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ dc = BoolProperty(
+ name="Delete Base Mesh(s)?",
+ default=True
+ )
+ wh = BoolProperty(
+ name="Stay Within Base Mesh(s)?",
+ description="Keeps cubes from exceeding base mesh bounds",
+ default=True
+ )
+ uf = BoolProperty(
+ name="Uniform Cube Quantity",
+ default=False
+ )
+ qn = IntProperty(
+ name="Cube Quantity",
+ default=10,
+ min=1, max=1500
+ )
+ mn = FloatVectorProperty(
+ name="Min Scales",
+ default=(0.1, 0.1, 0.1),
+ subtype='XYZ'
+ )
+ mx = FloatVectorProperty(
+ name="Max Scales",
+ default=(2.0, 2.0, 2.0),
+ subtype='XYZ'
+ )
+ lo = FloatVectorProperty(
+ name="XYZ Offset",
+ default=(0.0, 0.0, 0.0),
+ subtype='XYZ'
+ )
+ rsd = FloatProperty(
+ name="Random Seed",
+ default=1
+ )
+
+ def execute(self, context):
+ rsdchange = self.rsd
+ oblst = []
+ uvyes = 0
+ bpy.ops.group.create(name='Cubagrouper')
+ bpy.ops.group.objects_remove()
+ for ob in bpy.context.selected_objects:
+ oblst.append(ob)
+ for obj in oblst:
+ bpy.ops.object.select_pattern(pattern=obj.name) # Select base mesh
+ bpy.context.scene.objects.active = obj
+ if obj.data.uv_layers[:] != []:
+ uvyes = 1
+ else:
+ uvyes = 0
+ bpy.ops.object.group_link(group='Cubagrouper')
+ dim = obj.dimensions
+ rot = obj.rotation_euler
+ if self.uf is True:
+ area = dim.x * dim.y * dim.z
+ else:
+ area = 75
+ for cube in range(round((area / 75) * self.qn)):
+ random.seed(rsdchange)
+ pmn = self.mn # Proxy values
+ pmx = self.mx
+ if self.wh is True:
+ if dim.x < pmx.x: # Keeping things from exceeding proper size.
+ pmx.x = dim.x
+ if dim.y < pmx.y:
+ pmx.y = dim.y
+ if dim.z < pmx.z:
+ pmx.z = dim.z
+ if 0.0 > pmn.x: # Keeping things from going under zero.
+ pmn.x = 0.0
+ if 0.0 > pmn.y:
+ pmn.y = 0.0
+ if 0.0 > pmn.z:
+ pmn.z = 0.0
+ sx = (random.random() * (pmx.x - pmn.x)) + pmn.x # Just changed self.mx and .mn to pmx.
+ sy = (random.random() * (pmx.y - pmn.y)) + pmn.y
+ sz = (random.random() * (pmx.z - pmn.z)) + pmn.z
+ if self.wh is True: # This keeps the cubes within the base mesh.
+ ex = (random.random() * (dim.x - sx)) - ((dim.x - sx) / 2) + obj.location.x
+ wy = (random.random() * (dim.y - sy)) - ((dim.y - sy) / 2) + obj.location.y
+ ze = (random.random() * (dim.z - sz)) - ((dim.z - sz) / 2) + obj.location.z
+ elif self.wh is False:
+ ex = (random.random() * dim.x) - (dim.x / 2) + obj.location.x
+ wy = (random.random() * dim.y) - (dim.y / 2) + obj.location.y
+ ze = (random.random() * dim.z) - (dim.z / 2) + obj.location.z
+ bpy.ops.mesh.primitive_cube_add(
+ radius=0.5, location=(ex + self.lo.x, wy + self.lo.y, ze + self.lo.z)
+ )
+ bpy.ops.object.mode_set(mode='EDIT')
+ bpy.ops.mesh.select_all(action='SELECT')
+ bpy.ops.transform.resize(
+ value=(sx, sy, sz), constraint_axis=(True, True, True),
+ constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED',
+ proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True
+ )
+ bpy.ops.object.mode_set(mode='OBJECT')
+ select = bpy.context.object # This is used to keep something selected for poll().
+ bpy.ops.object.group_link(group='Cubagrouper')
+ rsdchange += 3
+ bpy.ops.object.select_grouped(type='GROUP')
+ bpy.ops.transform.rotate(
+ value=rot[0], axis=(1, 0, 0), constraint_axis=(False, False, False),
+ constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED',
+ proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True
+ )
+ bpy.ops.transform.rotate(
+ value=rot[1], axis=(0, 1, 0), constraint_axis=(False, False, False),
+ constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED',
+ proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True
+ )
+ bpy.ops.transform.rotate(
+ value=rot[2], axis=(0, 0, 1), constraint_axis=(False, False, False),
+ constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED',
+ proportional_edit_falloff='SMOOTH', proportional_size=1, release_confirm=True
+ )
+ bpy.context.scene.objects.active = obj # Again needed to avoid poll() taking me down.
+ bpy.ops.object.make_links_data(type='MODIFIERS')
+ bpy.ops.object.make_links_data(type='MATERIAL')
+ if uvyes == 1:
+ bpy.ops.object.join_uvs()
+ bpy.ops.group.objects_remove()
+ bpy.context.scene.objects.active = select
+ if self.dc is True:
+ bpy.context.scene.objects.unlink(obj)
+ return {'FINISHED'}
+
+ @classmethod
+ def poll(cls, context):
+ ob = context.active_object
+ return ob is not None and ob.mode == 'OBJECT'
+
+ def draw(self, context):
+ layout = self.layout
+ box = layout.box()
+ box.label(text="Options")
+ box.prop(self, "dc")
+ box.prop(self, "wh")
+ box.prop(self, "uf")
+ box = layout.box()
+ box.label(text="Parameters")
+ box.prop(self, "qn")
+ box.prop(self, "mn")
+ box.prop(self, "mx")
+ box.prop(self, "lo")
+ box.prop(self, "rsd")
+
+
+def add_object_button(self, context):
+ self.layout.operator(makestructure.bl_idname, text="Add Random Box structure", icon='PLUGIN')
+
+
+def register():
+ bpy.utils.register_class(makestructure)
+ bpy.types.INFO_MT_add.append(add_object_button)
+
+
+def unregister():
+ bpy.utils.unregister_class(makestructure)
+ bpy.types.INFO_MT_add.remove(add_object_button)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/rope_alpha.py b/add_advanced_objects/rope_alpha.py
new file mode 100644
index 00000000..f0406148
--- /dev/null
+++ b/add_advanced_objects/rope_alpha.py
@@ -0,0 +1,762 @@
+# Copyright (c) 2012 Jorge Hernandez - Melendez
+
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# TODO : translate comments, prop names into English, add missing tooltips
+
+bl_info = {
+ "name": "Rope Creator",
+ "description": "Dynamic rope (with cloth) creator",
+ "author": "Jorge Hernandez - Melenedez",
+ "version": (0, 2),
+ "blender": (2, 7, 3),
+ "location": "Left Toolbar > ClothRope",
+ "warning": "",
+ "wiki_url": "",
+ "tracker_url": "",
+ "category": "Add Mesh"
+}
+
+
+import bpy
+from bpy.types import Operator
+from bpy.props import (
+ BoolProperty,
+ FloatProperty,
+ IntProperty,
+ )
+
+
+def desocultar(quien):
+ if quien == "todo":
+ for ob in bpy.data.objects:
+ ob.hide = False
+ else:
+ bpy.data.objects[quien].hide = False
+
+
+def deseleccionar_todo():
+ bpy.ops.object.select_all(action='DESELECT')
+
+
+def seleccionar_todo():
+ bpy.ops.object.select_all(action='SELECT')
+
+
+def salir_de_editmode():
+ if bpy.context.mode == "EDIT" or bpy.context.mode == "EDIT_CURVE" or bpy.context.mode == "EDIT_MESH":
+ bpy.ops.object.mode_set(mode='OBJECT')
+
+# Clear scene:
+
+
+def reset_scene():
+ desocultar("todo")
+ # el play back al principio
+ bpy.ops.screen.frame_jump(end=False)
+ try:
+ salir_de_editmode()
+ except:
+ pass
+ area = bpy.context.area
+ # en el outliner expando todo para poder seleccionar los emptys hijos
+ old_type = area.type
+ area.type = 'OUTLINER'
+ bpy.ops.outliner.expanded_toggle()
+ area.type = old_type
+ # vuelvo al contexto donde estaba
+ seleccionar_todo()
+ bpy.ops.object.delete(use_global=False)
+
+
+def entrar_en_editmode():
+ if bpy.context.mode == "OBJECT":
+ bpy.ops.object.mode_set(mode='EDIT')
+
+
+def select_all_in_edit_mode(ob):
+ if ob.mode != 'EDIT':
+ entrar_en_editmode()
+ bpy.ops.mesh.select_all(action="DESELECT")
+ bpy.context.tool_settings.mesh_select_mode = (True, False, False)
+ salir_de_editmode()
+ for v in ob.data.vertices:
+ if not v.select:
+ v.select = True
+ entrar_en_editmode()
+ # bpy.ops.mesh.select_all(action="SELECT")
+
+
+def deselect_all_in_edit_mode(ob):
+ if ob.mode != 'EDIT':
+ entrar_en_editmode()
+ bpy.ops.mesh.select_all(action="DESELECT")
+ bpy.context.tool_settings.mesh_select_mode = (True, False, False)
+ salir_de_editmode()
+ for v in ob.data.vertices:
+ if not v.select:
+ v.select = False
+ entrar_en_editmode()
+
+
+def which_vertex_are_selected(ob):
+ for v in ob.data.vertices:
+ if v.select:
+ print(str(v.index))
+ print("el vertice " + str(v.index) + " esta seleccionado")
+
+
+def seleccionar_por_nombre(nombre):
+ scn = bpy.context.scene
+ bpy.data.objects[nombre].select = True
+ scn.objects.active = bpy.data.objects[nombre]
+
+
+def deseleccionar_por_nombre(nombre):
+ # scn = bpy.context.scene
+ bpy.data.objects[nombre].select = False
+
+
+def crear_vertices(ob):
+ ob.data.vertices.add(1)
+ ob.data.update
+
+
+def borrar_elementos_seleccionados(tipo):
+ if tipo == "vertices":
+ bpy.ops.mesh.delete(type='VERT')
+
+
+def tab_editmode():
+ bpy.ops.object.editmode_toggle()
+
+
+def obtener_coords_vertex_seleccionados():
+ coordenadas_de_vertices = []
+ for ob in bpy.context.selected_objects:
+ print(ob.name)
+ if ob.type == 'MESH':
+ for v in ob.data.vertices:
+ if v.select:
+ coordenadas_de_vertices.append([v.co[0], v.co[1], v.co[2]])
+ return coordenadas_de_vertices[0]
+
+
+def crear_locator(pos):
+ bpy.ops.object.empty_add(
+ type='PLAIN_AXES', radius=1, view_align=False,
+ location=(pos[0], pos[1], pos[2]),
+ layers=(True, False, False, False, False, False, False,
+ False, False, False, False, False, False, False,
+ False, False, False, False, False, False)
+ )
+
+
+def extruir_vertices(longitud, cuantos_segmentos):
+ bpy.ops.mesh.extrude_region_move(
+ MESH_OT_extrude_region={"mirror": False},
+ TRANSFORM_OT_translate={
+ "value": (longitud / cuantos_segmentos, 0, 0),
+ "constraint_axis": (True, False, False),
+ "constraint_orientation": 'GLOBAL', "mirror": False,
+ "proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
+ "proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
+ "snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
+ "gpencil_strokes": False, "texture_space": False,
+ "remove_on_cancel": False, "release_confirm": False
+ }
+ )
+
+
+def select_all_vertex_in_curve_bezier(bc):
+ for i in range(len(bc.data.splines[0].points)):
+ bc.data.splines[0].points[i].select = True
+
+
+def deselect_all_vertex_in_curve_bezier(bc):
+ for i in range(len(bc.data.splines[0].points)):
+ bc.data.splines[0].points[i].select = False
+
+
+def ocultar_relationships():
+ for area in bpy.context.screen.areas:
+ if area.type == 'VIEW_3D':
+ area.spaces[0].show_relationship_lines = False
+
+
+class ClothRope(Operator):
+ bl_idname = "clot.rope"
+ bl_label = "Rope Cloth"
+
+ ropelenght = IntProperty(
+ name="longitud",
+ default=5
+ )
+ ropesegments = IntProperty(
+ name="rsegments",
+ default=5
+ )
+ qcr = IntProperty(
+ name="qualcolr",
+ min=1, max=20,
+ default=20
+ )
+ substeps = IntProperty(
+ name="rsubsteps",
+ min=4, max=80,
+ default=50
+ )
+ resrope = IntProperty(
+ name="resr",
+ default=5
+ )
+ radiusrope = FloatProperty(
+ name="radius",
+ min=0.04, max=1,
+ default=0.04
+ )
+ hide_emptys = BoolProperty(
+ name="hemptys",
+ default=False
+ )
+
+ def execute(self, context):
+ # add new scene
+ bpy.ops.scene.new(type="NEW")
+ scene = bpy.context.scene
+ scene.name = "Test Rope"
+ seleccionar_todo()
+ longitud = self.ropelenght
+ # para que desde el primer punto hasta el ultimo, entre
+ # medias tenga x segmentos debo sumarle 1 a la cantidad:
+ cuantos_segmentos = self.ropesegments + 1
+ calidad_de_colision = self.qcr
+ substeps = self.substeps
+ deseleccionar_todo()
+ # creamos el empty que sera el padre de todo
+ bpy.ops.object.empty_add(
+ type='SPHERE', radius=1, view_align=False, location=(0, 0, 0),
+ layers=(True, False, False, False, False, False, False, False,
+ False, False, False, False, False, False, False, False,
+ False, False, False, False)
+ )
+ ob = bpy.context.selected_objects[0]
+ ob.name = "Rope"
+ deseleccionar_todo()
+ # creamos un plano y lo borramos
+ bpy.ops.mesh.primitive_plane_add(
+ radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0),
+ layers=(True, False, False, False, False, False, False, False, False,
+ False, False, False, False, False, False, False, False,
+ False, False, False)
+ )
+ ob = bpy.context.selected_objects[0]
+ # renombrar:
+ ob.name = "cuerda"
+ entrar_en_editmode() # entramos en edit mode
+ select_all_in_edit_mode(ob)
+ # seleccionar_todo() # ya viene por default seleccionado
+ borrar_elementos_seleccionados("vertices")
+ salir_de_editmode() # salimos de edit mode
+ crear_vertices(ob) # creamos un vertex
+ # creando el grupo Group para el PIN
+ # Group contiene los vertices del pin y Group.001 contiene la linea unica principal
+ entrar_en_editmode() # entramos en edit mode
+ bpy.ops.object.vertex_group_add() # creamos un grupo
+ select_all_in_edit_mode(ob)
+ bpy.ops.object.vertex_group_assign() # y lo asignamos
+ # los hooks van a la curva no a la guia poligonal...
+ # creo el primer hook sin necesidad de crear luego el locator a mano:
+ # bpy.ops.object.hook_add_newob()
+ salir_de_editmode() # salimos de edit mode
+ ob.vertex_groups[0].name = "Pin"
+ deseleccionar_todo()
+ seleccionar_por_nombre("cuerda")
+ # hago los extrudes del vertice:
+ for i in range(cuantos_segmentos):
+ entrar_en_editmode()
+ extruir_vertices(longitud, cuantos_segmentos)
+ # y los ELIMINO del grupo PIN
+ bpy.ops.object.vertex_group_remove_from()
+ # obtengo la direccion para lego crear el locator en su posicion
+ pos = obtener_coords_vertex_seleccionados()
+ # los hooks van a la curva no a la guia poligonal...
+ # creo el hook sin necesidad de crear el locator a mano:
+ # bpy.ops.object.hook_add_newob()
+ salir_de_editmode() # salimos de edit mode
+ # creo el locator en su sitio
+ crear_locator(pos)
+ deseleccionar_todo()
+ seleccionar_por_nombre("cuerda")
+ deseleccionar_todo()
+ seleccionar_por_nombre("cuerda")
+ # vuelvo a seleccionar la cuerda
+ entrar_en_editmode()
+ pos = obtener_coords_vertex_seleccionados() # y obtenemos su posicion
+ salir_de_editmode()
+ # creamos el ultimo locator
+ crear_locator(pos)
+ deseleccionar_todo()
+ seleccionar_por_nombre("cuerda")
+ entrar_en_editmode() # entramos en edit mode
+ bpy.ops.object.vertex_group_add() # CREANDO GRUPO GUIA MAESTRA
+ select_all_in_edit_mode(ob)
+ bpy.ops.object.vertex_group_assign() # y lo asignamos
+ ob.vertex_groups[1].name = "Guide_rope"
+ # extruimos la curva para que tenga un minimo grosor para colisionar
+ bpy.ops.mesh.extrude_region_move(
+ MESH_OT_extrude_region={"mirror": False},
+ TRANSFORM_OT_translate={
+ "value": (0, 0.005, 0), "constraint_axis": (False, True, False),
+ "constraint_orientation": 'GLOBAL', "mirror": False,
+ "proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
+ "proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
+ "snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
+ "gpencil_strokes": False, "texture_space": False,
+ "remove_on_cancel": False, "release_confirm": False
+ }
+ )
+ bpy.ops.object.vertex_group_remove_from()
+ deselect_all_in_edit_mode(ob)
+ salir_de_editmode()
+ bpy.ops.object.modifier_add(type='CLOTH')
+ bpy.context.object.modifiers["Cloth"].settings.use_pin_cloth = True
+ bpy.context.object.modifiers["Cloth"].settings.vertex_group_mass = "Pin"
+ bpy.context.object.modifiers["Cloth"].collision_settings.collision_quality = calidad_de_colision
+ bpy.context.object.modifiers["Cloth"].settings.quality = substeps
+ # DUPLICAMOS para convertir a curva:
+ # selecciono los vertices que forman parte del grupo Group.001
+ seleccionar_por_nombre("cuerda")
+ entrar_en_editmode()
+ bpy.ops.mesh.select_all(action="DESELECT")
+ bpy.context.tool_settings.mesh_select_mode = (True, False, False)
+ salir_de_editmode()
+ gi = ob.vertex_groups["Guide_rope"].index # get group index
+ for v in ob.data.vertices:
+ for g in v.groups:
+ if g.group == gi: # compare with index in VertexGroupElement
+ v.select = True
+ entrar_en_editmode()
+ # ya tenemos la guia seleccionada:
+ # la duplicamos:
+ bpy.ops.mesh.duplicate_move(
+ MESH_OT_duplicate={"mode": 1},
+ TRANSFORM_OT_translate={
+ "value": (0, 0, 0), "constraint_axis": (False, False, False),
+ "constraint_orientation": 'GLOBAL', "mirror": False,
+ "proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
+ "proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
+ "snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
+ "gpencil_strokes": False, "texture_space": False,
+ "remove_on_cancel": False, "release_confirm": False
+ }
+ )
+ # separamos por seleccion:
+ bpy.ops.mesh.separate(type='SELECTED')
+ salir_de_editmode()
+ deseleccionar_todo()
+ seleccionar_por_nombre("cuerda.001")
+ # a la nueva curva copiada le quitamos el cloth:
+ bpy.ops.object.modifier_remove(modifier="Cloth")
+ # la convertimos en curva:
+ bpy.ops.object.convert(target='CURVE')
+ # todos los emptys:
+ emptys = []
+ for eo in bpy.data.objects:
+ if eo.type == 'EMPTY':
+ if eo.name != "Rope":
+ emptys.append(eo)
+ # print(emptys)
+ # cuantos puntos tiene la becier:
+ # len(bpy.data.objects['cuerda.001'].data.splines[0].points)
+ # seleccionar y deseleccionar:
+ bc = bpy.data.objects['cuerda.001']
+ n = 0
+ for e in emptys:
+ deseleccionar_todo()
+ seleccionar_por_nombre(e.name)
+ seleccionar_por_nombre(bc.name)
+ entrar_en_editmode()
+ deselect_all_vertex_in_curve_bezier(bc)
+ bc.data.splines[0].points[n].select = True
+ bpy.ops.object.hook_add_selob(use_bone=False)
+ salir_de_editmode()
+ n = n + 1
+ # entrar_en_editmode()
+ ob = bpy.data.objects['cuerda']
+ n = 0
+ for e in emptys:
+ deseleccionar_todo()
+ seleccionar_por_nombre(e.name)
+ seleccionar_por_nombre(ob.name)
+ entrar_en_editmode()
+ bpy.ops.mesh.select_all(action="DESELECT")
+ bpy.context.tool_settings.mesh_select_mode = (True, False, False)
+ salir_de_editmode()
+ for v in ob.data.vertices:
+ if v.select:
+ v.select = False
+ ob.data.vertices[n].select = True
+ entrar_en_editmode()
+ bpy.ops.object.vertex_parent_set()
+ # deselect_all_in_edit_mode(ob)
+ salir_de_editmode()
+ n = n + 1
+
+ # ocultar los emptys:
+ # for e in emptys:
+ deseleccionar_todo()
+ # emparentando todo al empty esferico:
+ seleccionar_por_nombre("cuerda.001")
+ seleccionar_por_nombre("cuerda")
+ seleccionar_por_nombre("Rope")
+ bpy.ops.object.parent_set(type='OBJECT', keep_transform=True)
+ deseleccionar_todo()
+ # display que no muestre las relaciones
+ ocultar_relationships()
+ seleccionar_por_nombre("cuerda.001")
+ # cuerda curva settings:
+ bpy.context.object.data.fill_mode = 'FULL'
+ bpy.context.object.data.bevel_depth = self.radiusrope
+ bpy.context.object.data.bevel_resolution = self.resrope
+
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width=310)
+
+ def draw(self, context):
+ layout = self.layout
+ box = layout.box()
+ col = box.column()
+ col.label("Rope settings:")
+ rowsub0 = col.row()
+ rowsub0.prop(self, "ropelenght", text='Length')
+ rowsub0.prop(self, "ropesegments", text='Segments')
+ rowsub0.prop(self, "radiusrope", text='Radius')
+
+ col.label("Quality Settings:")
+ col.prop(self, "resrope", text='Resolution curve')
+ col.prop(self, "qcr", text='Quality Collision')
+ col.prop(self, "substeps", text='Substeps')
+
+
+class BallRope(Operator):
+ bl_idname = "ball.rope"
+ bl_label = "Rope Ball"
+
+ # defaults rope ball
+ ropelenght2 = IntProperty(
+ name="longitud",
+ default=10
+ )
+ ropesegments2 = IntProperty(
+ name="rsegments",
+ min=0, max=999,
+ default=6
+ )
+ radiuscubes = FloatProperty(
+ name="radius",
+ default=0.5
+ )
+ radiusrope = FloatProperty(
+ name="radius",
+ default=0.4
+ )
+ worldsteps = IntProperty(
+ name="worldsteps",
+ min=60, max=1000,
+ default=250
+ )
+ solveriterations = IntProperty(
+ name="solveriterations",
+ min=10, max=100,
+ default=50
+ )
+ massball = IntProperty(
+ name="massball",
+ default=1
+ )
+ resrope = IntProperty(
+ name="resolucion",
+ default=4
+ )
+ grados = FloatProperty(
+ name="grados",
+ default=45
+ )
+ separacion = FloatProperty(
+ name="separacion",
+ default=0.1
+ )
+ hidecubes = BoolProperty(
+ name="hidecubes",
+ default=False
+ )
+
+ def execute(self, context):
+ world_steps = self.worldsteps
+ solver_iterations = self.solveriterations
+ longitud = self.ropelenght2
+ # hago un + 2 para que los segmentos sean los que hay entre los dos extremos...
+ segmentos = self.ropesegments2 + 2
+ offset_del_suelo = 1
+ offset_del_suelo_real = (longitud / 2) + (segmentos / 2)
+ radio = self.radiuscubes
+ radiorope = self.radiusrope
+ masa = self.massball
+ resolucion = self.resrope
+ rotrope = self.grados
+ separation = self.separacion
+ hidecubeslinks = self.hidecubes
+ # add new scene
+ bpy.ops.scene.new(type="NEW")
+ scene = bpy.context.scene
+ scene.name = "Test Ball"
+ # suelo:
+ bpy.ops.mesh.primitive_cube_add(
+ radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0),
+ layers=(True, False, False, False, False, False, False, False, False,
+ False, False, False, False, False, False, False, False,
+ False, False, False)
+ )
+ bpy.context.object.scale.x = 10 + longitud
+ bpy.context.object.scale.y = 10 + longitud
+ bpy.context.object.scale.z = 0.05
+ bpy.context.object.name = "groundplane"
+ bpy.ops.rigidbody.objects_add(type='PASSIVE')
+ # creamos el primer cubo:
+ cuboslink = []
+ n = 0
+ for i in range(segmentos):
+ # si es 0 le digo que empieza desde 1
+ if i == 0:
+ i = offset_del_suelo
+ else: # si no es 0 les tengo que sumar uno para que no se pisen al empezar el primero desde 1
+ i = i + offset_del_suelo
+ separacion = longitud * 2 / segmentos # distancia entre los cubos link
+ bpy.ops.mesh.primitive_cube_add(
+ radius=1, view_align=False, enter_editmode=False,
+ location=(0, 0, i * separacion),
+ layers=(True, False, False, False, False, False, False, False,
+ False, False, False, False, False, False, False, False,
+ False, False, False, False)
+ )
+ bpy.ops.rigidbody.objects_add(type='ACTIVE')
+ bpy.context.object.name = "CubeLink"
+ if n != 0:
+ bpy.context.object.draw_type = 'WIRE'
+ bpy.context.object.hide_render = True
+ n += 1
+ bpy.context.object.scale.z = (longitud * 2) / (segmentos * 2) - separation
+ bpy.context.object.scale.x = radio
+ bpy.context.object.scale.y = radio
+ cuboslink.append(bpy.context.object)
+ for i in range(len(cuboslink)):
+ deseleccionar_todo()
+ if i != len(cuboslink) - 1:
+ nombre1 = cuboslink[i]
+ nombre2 = cuboslink[i + 1]
+ seleccionar_por_nombre(nombre1.name)
+ seleccionar_por_nombre(nombre2.name)
+ bpy.ops.rigidbody.connect()
+ seleccionar_por_nombre
+ for i in range(segmentos - 1):
+ if i == 0:
+ seleccionar_por_nombre("Constraint")
+ else:
+ if i <= 9 and i > 0:
+ seleccionar_por_nombre("Constraint.00" + str(i))
+ else:
+ if i <= 99 and i > 9:
+ seleccionar_por_nombre("Constraint.0" + str(i))
+ else:
+ if i <= 999 and i > 99:
+ seleccionar_por_nombre("Constraint." + str(i))
+ for c in bpy.context.selected_objects:
+ c.rigid_body_constraint.type = 'POINT'
+ deseleccionar_todo()
+
+ # creamos la curva bezier:
+ bpy.ops.curve.primitive_bezier_curve_add(
+ radius=1, view_align=False, enter_editmode=False, location=(0, 0, 0),
+ layers=(True, False, False, False, False, False, False, False, False,
+ False, False, False, False, False, False, False, False, False, False, False)
+ )
+ bpy.context.object.name = "Cuerda"
+ for i in range(len(cuboslink)):
+ cubonombre = cuboslink[i].name
+ seleccionar_por_nombre(cubonombre)
+ seleccionar_por_nombre("Cuerda")
+ x = cuboslink[i].location[0]
+ y = cuboslink[i].location[1]
+ z = cuboslink[i].location[2]
+ # si es 0 le digo que empieza desde 1 es el offset desde el suelo...
+ if i == 0:
+ i = offset_del_suelo
+ else: # si no es 0 les tengo que sumar uno para que no se pisen al empezar el primero desde 1
+ i = i + offset_del_suelo
+ salir_de_editmode()
+ # entrar_en_editmode()
+ tab_editmode()
+ if i == 1:
+ # selecciono todos los vertices y los borro
+ select_all_vertex_in_curve_bezier(bpy.data.objects["Cuerda"])
+ bpy.ops.curve.delete(type='VERT')
+ # creamos el primer vertice:
+ bpy.ops.curve.vertex_add(location=(x, y, z))
+ else:
+ # extruimos el resto:
+ bpy.ops.curve.extrude_move(
+ CURVE_OT_extrude={"mode": 'TRANSLATION'},
+ TRANSFORM_OT_translate={
+ "value": (0, 0, z / i),
+ "constraint_axis": (False, False, True),
+ "constraint_orientation": 'GLOBAL', "mirror": False,
+ "proportional": 'DISABLED', "proportional_edit_falloff": 'SMOOTH',
+ "proportional_size": 1, "snap": False, "snap_target": 'CLOSEST',
+ "snap_point": (0, 0, 0), "snap_align": False, "snap_normal": (0, 0, 0),
+ "gpencil_strokes": False, "texture_space": False,
+ "remove_on_cancel": False, "release_confirm": False
+ }
+ )
+ bpy.ops.object.hook_add_selob(use_bone=False)
+ salir_de_editmode()
+ bpy.context.object.data.bevel_resolution = resolucion
+ deseleccionar_todo()
+
+ # creando la esfera ball:
+ deseleccionar_todo()
+ seleccionar_por_nombre(cuboslink[0].name)
+ entrar_en_editmode()
+ z = cuboslink[0].scale.z + longitud / 2
+ bpy.ops.view3d.snap_cursor_to_selected()
+ bpy.ops.mesh.primitive_uv_sphere_add(
+ view_align=False, enter_editmode=False,
+ layers=(True, False, False, False, False, False, False,
+ False, False, False, False, False, False, False,
+ False, False, False, False, False, False)
+ )
+ bpy.ops.transform.translate(
+ value=(0, 0, -z + 2), constraint_axis=(False, False, True),
+ constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED',
+ proportional_edit_falloff='SMOOTH', proportional_size=1
+ )
+ bpy.ops.transform.resize(
+ value=(longitud / 2, longitud / 2, longitud / 2),
+ constraint_axis=(False, False, False),
+ constraint_orientation='GLOBAL',
+ mirror=False, proportional='DISABLED',
+ proportional_edit_falloff='SMOOTH', proportional_size=1
+ )
+ deselect_all_in_edit_mode(cuboslink[0])
+ salir_de_editmode()
+ bpy.ops.object.shade_smooth()
+ bpy.context.object.rigid_body.mass = masa
+ bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')
+
+ # lo subo todo para arriba un poco mas:
+ seleccionar_todo()
+ deseleccionar_por_nombre("groundplane")
+ bpy.ops.transform.translate(
+ value=(0, 0, offset_del_suelo_real),
+ constraint_axis=(False, False, True),
+ constraint_orientation='GLOBAL', mirror=False,
+ proportional='DISABLED', proportional_edit_falloff='SMOOTH',
+ proportional_size=1
+ )
+
+ deseleccionar_todo()
+ seleccionar_por_nombre(cuboslink[-1].name)
+ bpy.ops.rigidbody.objects_add(type='PASSIVE')
+
+ bpy.context.scene.rigidbody_world.steps_per_second = world_steps
+ bpy.context.scene.rigidbody_world.solver_iterations = solver_iterations
+
+ # para mover todo desde el primero de arriba:
+ seleccionar_por_nombre(cuboslink[-1].name)
+ bpy.ops.view3d.snap_cursor_to_selected()
+ seleccionar_todo()
+ deseleccionar_por_nombre("groundplane")
+ deseleccionar_por_nombre(cuboslink[-1].name)
+ bpy.context.space_data.pivot_point = 'CURSOR'
+ bpy.ops.transform.rotate(
+ value=rotrope, axis=(1, 0, 0),
+ constraint_axis=(True, False, False),
+ constraint_orientation='GLOBAL',
+ mirror=False, proportional='DISABLED',
+ proportional_edit_falloff='SMOOTH',
+ proportional_size=1
+ )
+ bpy.context.space_data.pivot_point = 'MEDIAN_POINT'
+ deseleccionar_todo()
+
+ seleccionar_por_nombre("Cuerda")
+ bpy.context.object.data.fill_mode = 'FULL'
+ bpy.context.object.data.bevel_depth = radiorope
+ for ob in bpy.data.objects:
+ if ob.name != cuboslink[0].name:
+ if ob.name.find("CubeLink") >= 0:
+ deseleccionar_todo()
+ seleccionar_por_nombre(ob.name)
+ if hidecubeslinks:
+ bpy.context.object.hide = True
+ ocultar_relationships()
+ deseleccionar_todo()
+ return {'FINISHED'}
+
+ def invoke(self, context, event):
+ return context.window_manager.invoke_props_dialog(self, width=310)
+
+ def draw(self, context):
+ layout = self.layout
+ box = layout.box()
+ col = box.column()
+ col.label("Rope settings:")
+ rowsub0 = col.row()
+ rowsub0.prop(self, "hidecubes", text='Hide Link Cubes')
+ rowsub1 = col.row()
+ rowsub1.prop(self, "ropelenght2", text='Length')
+ rowsub1.prop(self, "ropesegments2", text='Segments')
+ rowsub2 = col.row()
+ rowsub2.prop(self, "radiuscubes", text='Radius Link Cubes')
+ rowsub2.prop(self, "radiusrope", text='Radius Rope')
+ rowsub3 = col.row()
+ rowsub3.prop(self, "grados", text='Degrees')
+ rowsub3.prop(self, "separacion", text='Separation Link Cubes')
+
+ col.label("Quality Settings:")
+ col.prop(self, "resrope", text='Resolution Rope')
+ col.prop(self, "massball", text='Ball Mass')
+ col.prop(self, "worldsteps", text='World Steps')
+ col.prop(self, "solveriterations", text='Solver Iterarions')
+
+
+# Register
+
+def register():
+ bpy.utils.register_module(__name__)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+
+
+if __name__ == "__main__":
+ register()
diff --git a/add_advanced_objects/scene_objects_bi.py b/add_advanced_objects/scene_objects_bi.py
new file mode 100644
index 00000000..db3ec9cd
--- /dev/null
+++ b/add_advanced_objects/scene_objects_bi.py
@@ -0,0 +1,185 @@
+# gpl: author meta-androcto
+
+import bpy
+from bpy.types import Operator
+
+
+class add_BI_scene(Operator):
+ bl_idname = "bi.add_scene"
+ bl_label = "Create test scene"
+ bl_description = "BI Scene with Objects"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ blend_data = context.blend_data
+ # ob = bpy.context.active_object
+
+ # add new scene
+ bpy.ops.scene.new(type="NEW")
+ scene = bpy.context.scene
+ scene.name = "scene_materials"
+
+ # render settings
+ render = scene.render
+ render.resolution_x = 1920
+ render.resolution_y = 1080
+ render.resolution_percentage = 50
+
+ # add new world
+ world = bpy.data.worlds.new("Materials_World")
+ scene.world = world
+ world.use_sky_blend = True
+ world.use_sky_paper = True
+ world.horizon_color = (0.004393, 0.02121, 0.050)
+ world.zenith_color = (0.03335, 0.227, 0.359)
+ world.light_settings.use_ambient_occlusion = True
+ world.light_settings.ao_factor = 0.25
+
+ # add camera
+ bpy.ops.object.camera_add(
+ location=(7.48113, -6.50764, 5.34367),
+ rotation=(1.109319, 0.010817, 0.814928)
+ )
+ cam = bpy.context.active_object.data
+ cam.lens = 35
+ cam.draw_size = 0.1
+ bpy.ops.view3d.viewnumpad(type='CAMERA')
+
+ # add point lamp
+ bpy.ops.object.lamp_add(
+ type="POINT", location=(4.07625, 1.00545, 5.90386),
+ rotation=(0.650328, 0.055217, 1.866391)
+ )
+ lamp1 = bpy.context.active_object.data
+ lamp1.name = "Point_Right"
+ lamp1.energy = 1.0
+ lamp1.distance = 30.0
+ lamp1.shadow_method = "RAY_SHADOW"
+ lamp1.use_sphere = True
+
+ # add point lamp2
+ bpy.ops.object.lamp_add(
+ type="POINT", location=(-0.57101, -4.24586, 5.53674),
+ rotation=(1.571, 0, 0.785)
+ )
+ lamp2 = bpy.context.active_object.data
+ lamp2.name = "Point_Left"
+ lamp2.energy = 1.0
+ lamp2.distance = 30.0
+
+ # Add cube
+ bpy.ops.mesh.primitive_cube_add()
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.subdivide(number_cuts=2)
+ bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
+ bpy.ops.object.editmode_toggle()
+
+ cube = bpy.context.active_object
+ # add new material
+ cubeMaterial = blend_data.materials.new("Cube_Material")
+ bpy.ops.object.material_slot_add()
+ cube.material_slots[0].material = cubeMaterial
+ # Diffuse
+ cubeMaterial.preview_render_type = "CUBE"
+ cubeMaterial.diffuse_color = (1.000, 0.373, 0.00)
+ cubeMaterial.diffuse_shader = 'OREN_NAYAR'
+ cubeMaterial.diffuse_intensity = 1.0
+ cubeMaterial.roughness = 0.09002
+ # Specular
+ cubeMaterial.specular_color = (1.000, 0.800, 0.136)
+ cubeMaterial.specular_shader = "PHONG"
+ cubeMaterial.specular_intensity = 1.0
+ cubeMaterial.specular_hardness = 511.0
+ # Shading
+ cubeMaterial.ambient = 1.00
+ cubeMaterial.use_cubic = False
+ # Transparency
+ cubeMaterial.use_transparency = False
+ cubeMaterial.alpha = 0
+ # Mirror
+ cubeMaterial.raytrace_mirror.use = True
+ cubeMaterial.mirror_color = (1.000, 0.793, 0.0)
+ cubeMaterial.raytrace_mirror.reflect_factor = 0.394
+ cubeMaterial.raytrace_mirror.fresnel = 2.0
+ cubeMaterial.raytrace_mirror.fresnel_factor = 1.641
+ cubeMaterial.raytrace_mirror.fade_to = "FADE_TO_SKY"
+ cubeMaterial.raytrace_mirror.gloss_anisotropic = 1.0
+ # Shadow
+ cubeMaterial.use_transparent_shadows = True
+
+ # Add a texture
+ cubetex = blend_data.textures.new("CloudTex", type='CLOUDS')
+ cubetex.noise_type = 'SOFT_NOISE'
+ cubetex.noise_scale = 0.25
+ mtex = cubeMaterial.texture_slots.add()
+ mtex.texture = cubetex
+ mtex.texture_coords = 'ORCO'
+ mtex.scale = (0.800, 0.800, 0.800)
+ mtex.use_map_mirror = True
+ mtex.mirror_factor = 0.156
+ mtex.use_map_color_diffuse = True
+ mtex.diffuse_color_factor = 0.156
+ mtex.use_map_normal = True
+ mtex.normal_factor = 0.010
+ mtex.blend_type = "ADD"
+ mtex.use_rgb_to_intensity = True
+ mtex.color = (1.000, 0.207, 0.000)
+
+ # Add monkey
+ bpy.ops.mesh.primitive_monkey_add(location=(-0.1, 0.08901, 1.505))
+ bpy.ops.transform.rotate(value=(1.15019), axis=(0, 0, 1))
+ bpy.ops.transform.rotate(value=(-0.673882), axis=(0, 1, 0))
+ bpy.ops.transform.rotate(value=-0.055, axis=(1, 0, 0))
+ bpy.ops.object.modifier_add(type='SUBSURF')
+ bpy.ops.object.shade_smooth()
+ monkey = bpy.context.active_object
+ # add new material
+ monkeyMaterial = blend_data.materials.new("Monkey_Material")
+ bpy.ops.object.material_slot_add()
+ monkey.material_slots[0].material = monkeyMaterial
+ # Material settings
+ monkeyMaterial.preview_render_type = "MONKEY"
+ monkeyMaterial.diffuse_color = (0.239, 0.288, 0.288)
+ monkeyMaterial.specular_color = (0.604, 0.465, 0.136)
+ monkeyMaterial.diffuse_shader = 'LAMBERT'
+ monkeyMaterial.diffuse_intensity = 1.0
+ monkeyMaterial.specular_intensity = 0.3
+ monkeyMaterial.ambient = 0
+ monkeyMaterial.type = 'SURFACE'
+ monkeyMaterial.use_cubic = True
+ monkeyMaterial.use_transparency = False
+ monkeyMaterial.alpha = 0
+ monkeyMaterial.use_transparent_shadows = True
+ monkeyMaterial.raytrace_mirror.use = True
+ monkeyMaterial.raytrace_mirror.reflect_factor = 0.65
+ monkeyMaterial.raytrace_mirror.fade_to = "FADE_TO_MATERIAL"
+
+ # Add plane
+ bpy.ops.mesh.primitive_plane_add(
+ radius=50, view_align=False, enter_editmode=False, location=(0, 0, -1)
+ )
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.transform.rotate(
+ value=-0.8, axis=(0, 0, 1), constraint_axis=(False, False, True),
+ constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED',
+ proportional_edit_falloff='SMOOTH', proportional_size=1
+ )
+ bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
+ bpy.ops.object.editmode_toggle()
+ plane = bpy.context.active_object
+ # add new material
+ planeMaterial = blend_data.materials.new("Plane_Material")
+ bpy.ops.object.material_slot_add()
+ plane.material_slots[0].material = planeMaterial
+ # Material settings
+ planeMaterial.preview_render_type = "CUBE"
+ planeMaterial.diffuse_color = (0.2, 0.2, 0.2)
+ planeMaterial.specular_color = (0.604, 0.465, 0.136)
+ planeMaterial.specular_intensity = 0.3
+ planeMaterial.ambient = 0
+ planeMaterial.use_cubic = True
+ planeMaterial.use_transparency = False
+ planeMaterial.alpha = 0
+ planeMaterial.use_transparent_shadows = True
+
+ return {"FINISHED"}
diff --git a/add_advanced_objects/scene_objects_cycles.py b/add_advanced_objects/scene_objects_cycles.py
new file mode 100644
index 00000000..1e07b3e5
--- /dev/null
+++ b/add_advanced_objects/scene_objects_cycles.py
@@ -0,0 +1,130 @@
+# gpl: author meta-androcto
+
+import bpy
+from bpy.types import Operator
+
+
+class add_cycles_scene(Operator):
+ bl_idname = "objects_cycles.add_scene"
+ bl_label = "Create test scene"
+ bl_description = "Cycles Scene with Objects"
+ bl_options = {'REGISTER'}
+
+ def execute(self, context):
+ blend_data = context.blend_data
+ # ob = bpy.context.active_object
+
+ # add new scene
+ bpy.ops.scene.new(type="NEW")
+ scene = bpy.context.scene
+ bpy.context.scene.render.engine = 'CYCLES'
+ scene.name = "scene_object_cycles"
+
+ # render settings
+ render = scene.render
+ render.resolution_x = 1920
+ render.resolution_y = 1080
+ render.resolution_percentage = 50
+
+ # add new world
+ world = bpy.data.worlds.new("Cycles_Object_World")
+ scene.world = world
+ world.use_sky_blend = True
+ world.use_sky_paper = True
+ world.horizon_color = (0.004393, 0.02121, 0.050)
+ world.zenith_color = (0.03335, 0.227, 0.359)
+ world.light_settings.use_ambient_occlusion = True
+ world.light_settings.ao_factor = 0.25
+
+ # add camera
+ bpy.ops.object.camera_add(
+ location=(7.48113, -6.50764, 5.34367),
+ rotation=(1.109319, 0.010817, 0.814928)
+ )
+ cam = bpy.context.active_object.data
+ cam.lens = 35
+ cam.draw_size = 0.1
+ bpy.ops.view3d.viewnumpad(type='CAMERA')
+
+ # add point lamp
+ bpy.ops.object.lamp_add(
+ type="POINT", location=(4.07625, 1.00545, 5.90386),
+ rotation=(0.650328, 0.055217, 1.866391)
+ )
+ lamp1 = bpy.context.active_object.data
+ lamp1.name = "Point_Right"
+ lamp1.energy = 1.0
+ lamp1.distance = 30.0
+ lamp1.shadow_method = "RAY_SHADOW"
+ lamp1.use_sphere = True
+
+ # add point lamp2
+ bpy.ops.object.lamp_add(
+ type="POINT", location=(-0.57101, -4.24586, 5.53674),
+ rotation=(1.571, 0, 0.785)
+ )
+ lamp2 = bpy.context.active_object.data
+ lamp2.name = "Point_Left"
+ lamp2.energy = 1.0
+ lamp2.distance = 30.0
+
+ # Add cube
+ bpy.ops.mesh.primitive_cube_add()
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.mesh.subdivide(number_cuts=2)
+ bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
+ bpy.ops.object.editmode_toggle()
+ cube = bpy.context.active_object
+
+ # add cube material
+ cubeMaterial = blend_data.materials.new("Cycles_Cube_Material")
+ bpy.ops.object.material_slot_add()
+ cube.material_slots[0].material = cubeMaterial
+ # Diffuse
+ cubeMaterial.preview_render_type = "CUBE"
+ cubeMaterial.diffuse_color = (1.000, 0.373, 0.00)
+ # Cycles
+ cubeMaterial.use_nodes = True
+
+ # Add monkey
+ bpy.ops.mesh.primitive_monkey_add(location=(-0.1, 0.08901, 1.505))
+ bpy.ops.transform.rotate(value=(1.15019), axis=(0, 0, 1))
+ bpy.ops.transform.rotate(value=(-0.673882), axis=(0, 1, 0))
+ bpy.ops.transform.rotate(value=-0.055, axis=(1, 0, 0))
+
+ bpy.ops.object.modifier_add(type='SUBSURF')
+ bpy.ops.object.shade_smooth()
+ monkey = bpy.context.active_object
+
+ # add monkey material
+ monkeyMaterial = blend_data.materials.new("Cycles_Monkey_Material")
+ bpy.ops.object.material_slot_add()
+ monkey.material_slots[0].material = monkeyMaterial
+ # Diffuse
+ monkeyMaterial.preview_render_type = "MONKEY"
+ monkeyMaterial.diffuse_color = (0.239, 0.288, 0.288)
+ # Cycles
+ monkeyMaterial.use_nodes = True
+
+ # Add plane
+ bpy.ops.mesh.primitive_plane_add(
+ radius=50, view_align=False,
+ enter_editmode=False, location=(0, 0, -1)
+ )
+ bpy.ops.object.editmode_toggle()
+ bpy.ops.transform.rotate(value=-0.8, axis=(0, 0, 1), constraint_axis=(False, False, True))
+ bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
+ bpy.ops.object.editmode_toggle()
+ plane = bpy.context.active_object
+
+ # add plane material
+ planeMaterial = blend_data.materials.new("Cycles_Plane_Material")
+ bpy.ops.object.material_slot_add()
+ plane.material_slots[0].material = planeMaterial
+ # Diffuse
+ planeMaterial.preview_render_type = "FLAT"
+ planeMaterial.diffuse_color = (0.2, 0.2, 0.2)
+ # Cycles
+ planeMaterial.use_nodes = True
+
+ return {'FINISHED'}
diff --git a/add_advanced_objects/scene_texture_render.py b/add_advanced_objects/scene_texture_render.py
new file mode 100644
index 00000000..2025b979
--- /dev/null
+++ b/add_advanced_objects/scene_texture_render.py
@@ -0,0 +1,66 @@
+# gpl: author meta-androcto
+
+import bpy
+from bpy.types import Operator
+
+
+class add_texture_scene(Operator):
+ bl_idname = "objects_texture.add_scene"
+ bl_label = "Create test scene"
+ bl_description = "Cycles Scene: Camera aligned to plane"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ def execute(self, context):
+ blend_data = context.blend_data
+ # ob = bpy.context.active_object
+
+ # add new scene
+ bpy.ops.scene.new(type="NEW")
+ scene = bpy.context.scene
+ bpy.context.scene.render.engine = 'CYCLES'
+ scene.name = "scene_texture_cycles"
+
+ # render settings
+ render = scene.render
+ render.resolution_x = 1080
+ render.resolution_y = 1080
+ render.resolution_percentage = 100
+
+ # add new world
+ world = bpy.data.worlds.new("Cycles_Textures_World")
+ scene.world = world
+ world.use_sky_blend = True
+ world.use_sky_paper = True
+ world.horizon_color = (0.004393, 0.02121, 0.050)
+ world.zenith_color = (0.03335, 0.227, 0.359)
+ world.light_settings.use_ambient_occlusion = True
+ world.light_settings.ao_factor = 0.5
+
+ # add camera
+ bpy.ops.view3d.viewnumpad(type='TOP')
+ bpy.ops.object.camera_add(location=(0, 0, 2.1850), rotation=(0, 0, 0), view_align=True)
+ cam = bpy.context.active_object.data
+ cam.lens = 35
+ cam.draw_size = 0.1
+
+ # add plane
+ bpy.ops.mesh.primitive_plane_add(enter_editmode=True, location=(0, 0, 0))
+ bpy.ops.mesh.subdivide(number_cuts=10, smoothness=0)
+ bpy.ops.uv.unwrap(method='CONFORMAL', margin=0.001)
+ bpy.ops.object.editmode_toggle()
+ plane = bpy.context.active_object
+
+ # add plane material
+ planeMaterial = blend_data.materials.new("Cycles_Plane_Material")
+ bpy.ops.object.material_slot_add()
+ plane.material_slots[0].material = planeMaterial
+ # Diffuse
+ planeMaterial.preview_render_type = "FLAT"
+ planeMaterial.diffuse_color = (0.2, 0.2, 0.2)
+ # Cycles
+ planeMaterial.use_nodes = True
+
+ # Back to Scene
+ sc = bpy.context.scene
+ bpy.ops.view3d.viewnumpad(type='CAMERA')
+ return {'FINISHED'}
diff --git a/add_advanced_objects/trilighting.py b/add_advanced_objects/trilighting.py
new file mode 100644
index 00000000..d23b4dbc
--- /dev/null
+++ b/add_advanced_objects/trilighting.py
@@ -0,0 +1,225 @@
+# gpl: author Daniel Schalla
+
+import bpy
+from bpy.types import Operator
+from bpy.props import (
+ EnumProperty,
+ FloatProperty,
+ IntProperty,
+ )
+from math import (
+ sin,
+ cos,
+ radians,
+ sqrt,
+ )
+
+
+class TriLighting(Operator):
+ bl_idname = "object.trilighting"
+ bl_label = "Tri-Lighting Creator"
+ bl_description = ("Add 3 Point Lighting to Selected / Active Object\n"
+ "Needs an active object in the scene")
+ bl_options = {'REGISTER', 'UNDO'}
+
+ height = FloatProperty(
+ name="Height",
+ default=5
+ )
+ distance = FloatProperty(
+ name="Distance",
+ default=5,
+ min=0.1,
+ subtype="DISTANCE"
+ )
+ energy = IntProperty(
+ name="Base Energy",
+ default=3,
+ min=1
+ )
+ contrast = IntProperty(
+ name="Contrast",
+ default=50,
+ min=-100, max=100,
+ subtype="PERCENTAGE"
+ )
+ leftangle = IntProperty(
+ name="Left Angle",
+ default=26,
+ min=1, max=90,
+ subtype="ANGLE"
+ )
+ rightangle = IntProperty(
+ name="Right Angle",
+ default=45,
+ min=1, max=90,
+ subtype="ANGLE"
+ )
+ backangle = IntProperty(
+ name="Back Angle",
+ default=235,
+ min=90, max=270,
+ subtype="ANGLE"
+ )
+ Light_Type_List = [('POINT', 'Point', 'Point Light'),
+ ('SUN', 'Sun', 'Sun Light'),
+ ('SPOT', 'Spot', 'Spot Light'),
+ ('HEMI', 'Hemi', 'Hemi Light'),
+ ('AREA', 'Area', 'Area Light')]
+ primarytype = EnumProperty(
+ attr='tl_type',
+ name="Key Type",
+ description="Choose the type off Key Light you would like",
+ items=Light_Type_List,
+ default='HEMI'
+ )
+ secondarytype = EnumProperty(
+ attr='tl_type',
+ name="Fill + Back Type",
+ description="Choose the type off secondary Light you would like",
+ items=Light_Type_List,
+ default="POINT"
+ )
+
+ @classmethod
+ def poll(cls, context):
+ return context.active_object is not None
+
+ def draw(self, context):
+ layout = self.layout
+
+ layout.label("Position:")
+ col = layout.column(align=True)
+ col.prop(self, "height")
+ col.prop(self, "distance")
+
+ layout.label("Light:")
+ col = layout.column(align=True)
+ col.prop(self, "energy")
+ col.prop(self, "contrast")
+
+ layout.label("Orientation:")
+ col = layout.column(align=True)
+ col.prop(self, "leftangle")
+ col.prop(self, "rightangle")
+ col.prop(self, "backangle")
+
+ col = layout.column()
+ col.label("Key Light Type:")
+ col.prop(self, "primarytype", text="")
+ col.label("Fill + Back Type:")
+ col.prop(self, "secondarytype", text="")
+
+ def execute(self, context):
+ scene = context.scene
+ view = context.space_data
+ if view.type == 'VIEW_3D' and not view.lock_camera_and_layers:
+ camera = view.camera
+ else:
+ camera = scene.camera
+
+ if (camera is None):
+ cam_data = bpy.data.cameras.new(name='Camera')
+ cam_obj = bpy.data.objects.new(name='Camera', object_data=cam_data)
+ scene.objects.link(cam_obj)
+ scene.camera = cam_obj
+ bpy.ops.view3d.camera_to_view()
+ camera = cam_obj
+ bpy.ops.view3d.viewnumpad(type='TOP')
+
+ obj = bpy.context.scene.objects.active
+
+ # Calculate Energy for each Lamp
+ if(self.contrast > 0):
+ keyEnergy = self.energy
+ backEnergy = (self.energy / 100) * abs(self.contrast)
+ fillEnergy = (self.energy / 100) * abs(self.contrast)
+ else:
+ keyEnergy = (self.energy / 100) * abs(self.contrast)
+ backEnergy = self.energy
+ fillEnergy = self.energy
+
+ # Calculate Direction for each Lamp
+
+ # Calculate current Distance and get Delta
+ obj_position = obj.location
+ cam_position = camera.location
+
+ delta_position = cam_position - obj_position
+ vector_length = sqrt(
+ (pow(delta_position.x, 2) +
+ pow(delta_position.y, 2) +
+ pow(delta_position.z, 2))
+ )
+ if not vector_length:
+ # division by zero most likely
+ self.report({'WARNING'}, "Operation Cancelled. No viable object in the scene")
+ return {'CANCELLED'}
+
+ single_vector = (1 / vector_length) * delta_position
+
+ # Calc back position
+ singleback_vector = single_vector.copy()
+ singleback_vector.x = cos(radians(self.backangle)) * single_vector.x + \
+ (-sin(radians(self.backangle)) * single_vector.y)
+
+ singleback_vector.y = sin(radians(self.backangle)) * single_vector.x + \
+ (cos(radians(self.backangle)) * single_vector.y)
+
+ backx = obj_position.x + self.distance * singleback_vector.x
+ backy = obj_position.y + self.distance * singleback_vector.y
+
+ backData = bpy.data.lamps.new(name="TriLamp-Back", type=self.secondarytype)
+ backData.energy = backEnergy
+
+ backLamp = bpy.data.objects.new(name="TriLamp-Back", object_data=backData)
+ scene.objects.link(backLamp)
+ backLamp.location = (backx, backy, self.height)
+
+ trackToBack = backLamp.constraints.new(type="TRACK_TO")
+ trackToBack.target = obj
+ trackToBack.track_axis = "TRACK_NEGATIVE_Z"
+ trackToBack.up_axis = "UP_Y"
+
+ # Calc right position
+ singleright_vector = single_vector.copy()
+ singleright_vector.x = cos(radians(self.rightangle)) * single_vector.x + \
+ (-sin(radians(self.rightangle)) * single_vector.y)
+
+ singleright_vector.y = sin(radians(self.rightangle)) * single_vector.x + \
+ (cos(radians(self.rightangle)) * single_vector.y)
+
+ rightx = obj_position.x + self.distance * singleright_vector.x
+ righty = obj_position.y + self.distance * singleright_vector.y
+
+ rightData = bpy.data.lamps.new(name="TriLamp-Fill", type=self.secondarytype)
+ rightData.energy = fillEnergy
+ rightLamp = bpy.data.objects.new(name="TriLamp-Fill", object_data=rightData)
+ scene.objects.link(rightLamp)
+ rightLamp.location = (rightx, righty, self.height)
+ trackToRight = rightLamp.constraints.new(type="TRACK_TO")
+ trackToRight.target = obj
+ trackToRight.track_axis = "TRACK_NEGATIVE_Z"
+ trackToRight.up_axis = "UP_Y"
+
+ # Calc left position
+ singleleft_vector = single_vector.copy()
+ singleleft_vector.x = cos(radians(-self.leftangle)) * single_vector.x + \
+ (-sin(radians(-self.leftangle)) * single_vector.y)
+ singleleft_vector.y = sin(radians(-self.leftangle)) * single_vector.x + \
+ (cos(radians(-self.leftangle)) * single_vector.y)
+ leftx = obj_position.x + self.distance * singleleft_vector.x
+ lefty = obj_position.y + self.distance * singleleft_vector.y
+
+ leftData = bpy.data.lamps.new(name="TriLamp-Key", type=self.primarytype)
+ leftData.energy = keyEnergy
+
+ leftLamp = bpy.data.objects.new(name="TriLamp-Key", object_data=leftData)
+ scene.objects.link(leftLamp)
+ leftLamp.location = (leftx, lefty, self.height)
+ trackToLeft = leftLamp.constraints.new(type="TRACK_TO")
+ trackToLeft.target = obj
+ trackToLeft.track_axis = "TRACK_NEGATIVE_Z"
+ trackToLeft.up_axis = "UP_Y"
+
+ return {'FINISHED'}
diff --git a/add_advanced_objects/unfold_transition.py b/add_advanced_objects/unfold_transition.py
new file mode 100644
index 00000000..a621ca54
--- /dev/null
+++ b/add_advanced_objects/unfold_transition.py
@@ -0,0 +1,337 @@
+bl_info = {
+ "name": "Unfold transition",
+ "version": (0, 1, 0),
+ "location": "Tool bar > Animation tab > UnFold Transition",
+ "description": "Simple unfold transition / animation, will separate faces and set up an armature",
+ "category": "Animation"}
+
+import bpy
+from bpy.props import (
+ BoolProperty,
+ EnumProperty,
+ FloatProperty,
+ IntProperty,
+ # PointerProperty,
+ )
+from bpy.types import (
+ Operator,
+ Panel,
+ )
+from random import (
+ randint,
+ uniform,
+ )
+from mathutils import Vector
+from mathutils.geometry import intersect_point_line
+
+
+bpy.types.WindowManager.modo = EnumProperty(
+ name="",
+ items=[("cursor", "3D Cursor", "Use the Distance to 3D Cursor"),
+ ("weight", "Weight Map", "Use a Painted Weight map"),
+ ("index", "Mesh Indices", "Use Faces and Vertices index")],
+ description="How to Sort Bones for animation", default="cursor"
+ )
+bpy.types.WindowManager.flip = BoolProperty(
+ name="Flipping Faces",
+ default=False,
+ description="Rotate faces around the Center & skip Scaling - "
+ "keep checked for both operators"
+ )
+bpy.types.WindowManager.fold_duration = IntProperty(
+ name="Total Time",
+ min=5, soft_min=25,
+ max=10000, soft_max=2500,
+ default=200,
+ description="Total animation length"
+ )
+bpy.types.WindowManager.sca_time = IntProperty(
+ name="Scale Time",
+ min=1,
+ max=5000, soft_max=500,
+ default=10,
+ description="Faces scaling time"
+ )
+bpy.types.WindowManager.rot_time = IntProperty(
+ name="Rotation Time",
+ min=1, soft_min=5,
+ max=5000, soft_max=500,
+ default=15,
+ description="Faces rotation time"
+ )
+bpy.types.WindowManager.rot_max = IntProperty(
+ name="Angle",
+ min=-180,
+ max=180,
+ default=135,
+ description="Faces rotation angle"
+ )
+bpy.types.WindowManager.fold_noise = IntProperty(
+ name="Noise",
+ min=0,
+ max=500, soft_max=50,
+ default=0,
+ description="Offset some faces animation"
+ )
+bpy.types.WindowManager.bounce = FloatProperty(
+ name="Bounce",
+ min=0,
+ max=10, soft_max=2.5,
+ default=0,
+ description="Add some bounce to rotation"
+ )
+bpy.types.WindowManager.from_point = BoolProperty(
+ name="Point",
+ default=False,
+ description="Scale faces from a Point instead of from an Edge"
+ )
+bpy.types.WindowManager.wiggle_rot = BoolProperty(
+ name="Wiggle",
+ default=False,
+ description="Use all Axis + Random Rotation instead of X Aligned"
+ )
+
+
+class Set_Up_Fold(Operator):
+ bl_idname = "object.set_up_fold"
+ bl_label = "Set Up Unfold"
+ bl_description = "Set up Faces and Bones for animation"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ return (bpy.context.object.type == "MESH")
+
+ def execute(self, context):
+ bpy.ops.object.mode_set()
+ wm = context.window_manager
+ scn = bpy.context.scene
+ obj = bpy.context.object
+ dat = obj.data
+ fac = dat.polygons
+ ver = dat.vertices
+
+ # try to cleanup traces of previous actions
+ bpy.ops.object.mode_set(mode="EDIT")
+ bpy.ops.mesh.remove_doubles(threshold=0.0001, use_unselected=True)
+ bpy.ops.object.mode_set()
+ old_vg = [vg for vg in obj.vertex_groups if vg.name.startswith("bone.")]
+ for vg in old_vg:
+ obj.vertex_groups.remove(vg)
+ if "UnFold" in obj.modifiers:
+ arm = obj.modifiers["UnFold"].object
+ rig = arm.data
+ try:
+ scn.objects.unlink(arm)
+ bpy.data.objects.remove(arm)
+ bpy.data.armatures.remove(rig)
+ except:
+ pass
+ obj.modifiers.remove(obj.modifiers["UnFold"])
+
+ # try to obtain the face sequence from the vertex weights
+ if wm.modo == "weight":
+ if len(obj.vertex_groups):
+ i = obj.vertex_groups.active.index
+ W = []
+ for f in fac:
+ v_data = []
+ for v in f.vertices:
+ try:
+ w = ver[v].groups[i].weight
+ v_data.append((w, v))
+ except:
+ v_data.append((0, v))
+ v_data.sort(reverse=True)
+ v1 = ver[v_data[0][1]].co
+ v2 = ver[v_data[1][1]].co
+ cen = Vector(f.center)
+ its = intersect_point_line(cen, v2, v1)
+ head = v2.lerp(v1, its[1])
+ peso = sum([x[0] for x in v_data])
+ W.append((peso, f.index, cen, head))
+ W.sort(reverse=True)
+ S = [x[1:] for x in W]
+ else:
+ self.report({"INFO"}, "First paint a Weight Map for this object")
+ return {"FINISHED"}
+
+ # separate the faces and sort them
+ bpy.ops.object.mode_set(mode="EDIT")
+ bpy.ops.mesh.select_all(action="SELECT")
+ bpy.ops.mesh.edge_split()
+ bpy.ops.mesh.select_all(action="SELECT")
+ if wm.modo == "cursor":
+ bpy.context.tool_settings.mesh_select_mode = [True, True, True]
+ bpy.ops.mesh.sort_elements(type="CURSOR_DISTANCE", elements={"VERT", "EDGE", "FACE"})
+ bpy.context.tool_settings.mesh_select_mode = [False, False, True]
+ bpy.ops.object.mode_set()
+
+ # Get sequence of faces and edges from the face / vertex indices
+ if wm.modo != "weight":
+ S = []
+ for f in fac:
+ E = list(f.edge_keys)
+ E.sort()
+ v1 = ver[E[0][0]].co
+ v2 = ver[E[0][1]].co
+ cen = Vector(f.center)
+ its = intersect_point_line(cen, v2, v1)
+ head = v2.lerp(v1, its[1])
+ S.append((f.index, f.center, head))
+
+ # create the armature and the modifier
+ arm = bpy.data.armatures.new("arm")
+ rig = bpy.data.objects.new("rig_" + obj.name, arm)
+ rig.matrix_world = obj.matrix_world
+ scn.objects.link(rig)
+ scn.objects.active = rig
+ bpy.ops.object.mode_set(mode="EDIT")
+ arm.draw_type = "WIRE"
+ rig.show_x_ray = True
+ mod = obj.modifiers.new("UnFold", "ARMATURE")
+ mod.show_in_editmode = True
+ mod.object = rig
+
+ # create bones and vertex groups
+ root = arm.edit_bones.new("bone.000")
+ root.tail = (0, 0, 0)
+ root.head = (0, 0, 1)
+ root.select = True
+ vis = [False, True] + [False] * 30
+
+ for fb in S:
+ f = fac[fb[0]]
+ b = arm.edit_bones.new("bone.000")
+ if wm.flip:
+ b.tail, b.head = fb[2], fb[1]
+ else:
+ b.tail, b.head = fb[1], fb[2]
+ b.align_roll(f.normal)
+ b.select = False
+ b.layers = vis
+ b.parent = root
+ vg = obj.vertex_groups.new(b.name)
+ vg.add(f.vertices, 1, "ADD")
+
+ bpy.ops.object.mode_set()
+ if wm.modo == "weight":
+ obj.vertex_groups.active_index = 0
+ scn.objects.active = rig
+ obj.select = False
+
+ return {"FINISHED"}
+
+
+class Animate_Fold(Operator):
+ bl_idname = "object.animate_fold"
+ bl_label = "Animate Unfold"
+ bl_description = "Animate bones to simulate unfold... Starts on current frame"
+ bl_options = {"REGISTER", "UNDO"}
+
+ @classmethod
+ def poll(cls, context):
+ obj = bpy.context.object
+ return (obj.type == "ARMATURE" and obj.is_visible(bpy.context.scene))
+
+ def execute(self, context):
+ obj = bpy.context.object
+ scn = bpy.context.scene
+ fra = scn.frame_current
+ wm = context.window_manager
+
+ # clear the animation and get the list of bones
+ if obj.animation_data:
+ obj.animation_data_clear()
+ bpy.ops.object.mode_set(mode="POSE")
+ bones = obj.pose.bones[0].children_recursive
+ if wm.flip:
+ rot = -3.141592
+ else:
+ rot = wm.rot_max / 57.3
+ extra = wm.rot_time * wm.bounce
+ ruido = max(wm.rot_time + extra, wm.sca_time) + wm.fold_noise
+ vel = (wm.fold_duration - ruido) / len(bones)
+
+ # introduce scale and rotation keyframes
+ for a, b in enumerate(bones):
+ t = fra + a * vel + randint(0, wm.fold_noise)
+ if wm.flip:
+ b.scale = (1, 1, 1)
+ elif wm.from_point:
+ b.scale = (0, 0, 0)
+ else:
+ b.scale = (1, 0, 0)
+ if not wm.flip:
+ b.keyframe_insert("scale", frame=t)
+ b.scale = (1, 1, 1)
+ b.keyframe_insert("scale", frame=t + wm.sca_time)
+ if wm.rot_max:
+ b.rotation_mode = "XYZ"
+ if wm.wiggle_rot:
+ euler = (uniform(-rot, rot), uniform(-rot, rot), uniform(-rot, rot))
+ else:
+ euler = (rot, 0, 0)
+ b.rotation_euler = euler
+ b.keyframe_insert("rotation_euler", frame=t)
+ if wm.bounce:
+ val = wm.bounce * -.10
+ b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2])
+ b.keyframe_insert("rotation_euler", frame=t + wm.rot_time + .25 * extra)
+ val = wm.bounce * .05
+ b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2])
+ b.keyframe_insert("rotation_euler", frame=t + wm.rot_time + .50 * extra)
+ val = wm.bounce * -.025
+ b.rotation_euler = (val * euler[0], val * euler[1], val * euler[2])
+ b.keyframe_insert("rotation_euler", frame=t + wm.rot_time + .75 * extra)
+ b.rotation_euler = (0, 0, 0)
+ b.keyframe_insert("rotation_euler", frame=t + wm.rot_time + extra)
+
+ return {"FINISHED"}
+
+
+class PanelFOLD(Panel):
+ bl_label = "Unfold Transition"
+ bl_space_type = "VIEW_3D"
+ bl_region_type = "TOOLS"
+ bl_category = "Create"
+ bl_context = "objectmode"
+ bl_options = {"DEFAULT_CLOSED"}
+
+ def draw(self, context):
+ wm = context.window_manager
+ layout = self.layout
+ column = layout.column()
+ column.operator("object.set_up_fold", text="1. Set Up Unfold")
+ column.prop(wm, "modo")
+ column.prop(wm, "flip")
+ layout.separator()
+ column = layout.column()
+ column.operator("object.animate_fold", text="2. Animate Unfold")
+ column.prop(wm, "fold_duration")
+ column.prop(wm, "sca_time")
+ column.prop(wm, "rot_time")
+ column.prop(wm, "rot_max")
+ row = column.row(align=True)
+ row.prop(wm, "fold_noise")
+ row.prop(wm, "bounce")
+ row = column.row(align=True)
+ row.prop(wm, "wiggle_rot")
+ if not wm.flip:
+ row.prop(wm, "from_point")
+
+
+def register():
+ bpy.utils.register_class(Set_Up_Fold)
+ bpy.utils.register_class(Animate_Fold)
+ bpy.utils.register_class(PanelFOLD)
+
+
+def unregister():
+ bpy.utils.unregister_class(Set_Up_Fold)
+ bpy.utils.unregister_class(Animate_Fold)
+ bpy.utils.unregister_class(PanelFOLD)
+
+
+if __name__ == "__main__":
+ register()