Featured post

new redirect for blender.org bpy docs.

http://www.blender.org/api/blender_python_api_current/ As of 10/11 november 2015 we can now link to the current api docs and not be worr...

July 27, 2011

ready to generate geometry - edge fillet blender 2.5


the code is a little lengthy to post in its entirety, but you can download it here
def get_correct_verts(arc_centre, arc_start, arc_end, NUM_VERTS, context):

obj_centre = context.object.location
axis = mathutils.geometry.normal(arc_centre, arc_end, arc_start)

point1 = arc_start - arc_centre
point2 = arc_end - arc_centre
main_angle = point1.angle(point2)
main_angle_degrees = math.degrees(main_angle)

div_angle = main_angle / (NUM_VERTS - 1)

if DEBUG == True:
print("arc_centre =", arc_centre)
print("arc_start =", arc_start)
print("arc_end =", arc_end)
print("NUM_VERTS =", NUM_VERTS)

print("NormalAxis1 =", axis)
print("Main Angle (Rad)", main_angle, " > degrees", main_angle_degrees)
print("Division Angle (Radians)", div_angle)
print("AXIS:", axis)

trig_arc_verts = []

for i in range(NUM_VERTS):
rotation_matrix = mathutils.Matrix.Rotation(i*-div_angle, 3, axis)
# trig_point = (arc_start - obj_centre - arc_centre) * rotation_matrix # old
trig_point = rotation_matrix * (arc_start - obj_centre - arc_centre) # new
trig_point += obj_centre + arc_centre
trig_arc_verts.append(trig_point)

return trig_arc_verts
Matrix.Rotate() was the most complicated part, but now i've broken through that barrier it doesn't seem such utter voodoo anymore. Warning to user: you must apply scale/location/rotation transforms to your profile/object first before running the code or the vertices will appear somewhere else. Not sure if there is a cute way around that.

small update




blender 2.5 GL fillet v0.2 from zeffii stanton on Vimeo.

July 26, 2011

more opengl drawing to viewport blender 2.5

update to the code below can be found h e r e


if DRAW_POINTS is true then the gl drawing includes Vertex drawing like this:


import bpy
import bgl
import mathutils
import bpy_extras
import math

from mathutils import Vector
from mathutils.geometry import interpolate_bezier as bezlerp
from bpy_extras.view3d_utils import location_3d_to_region_2d as loc3d2d

# OBJECTIVE

# fillet the selected vertex of a profile by dragging the mouse inwards
# the max fillet radius is reached at the shortest delta of surrounding verts.


# [x] MILESTONE 1
# [x] get selected vertex index.
# [x] get indices of attached verts.
# [x] get their lengths, return shortest.
# [x] shortest length is max fillet radius.

# [x] MILESTONE 2
# [x] draw gl bevel line from both edges (bev_edge1, bev_edge2)
# [x] find center of bevel line
# [x] draw line of radius length from found_index through center of bevel line.
# [x] call new found point 'fillet_centre"
# [x] draw pline from bev_edge1 to 2, with distance from fillet_centre (kappa)
# [x] draw pline from bev_edge1 to 2, with distance from fillet_centre (trig)

# [ ] MILESTONE 3
# [x] draw faux vertices
# [ ] make shift+rightlick, draw a line from selected vertex to mouse cursor.
# [x] draw opengl filleted line.
# [ ] allow mouse wheel to define segment numbers.
# [ ] enter to accept, and make real. esc to cancel.


# ==============================================================================

''' temporary constants '''

NUM_SEGS = 15
NUM_VERTS = NUM_SEGS + 1
HALF_RAD = 0.5
# KAPPA = 0.5522847498 # approximates the circle, fascinating magic number!
KAPPA = 4 * (( math.sqrt(2) - 1) / 3 )



''' switches '''

mode = 'TRIG'
DRAW_POINTS = True



''' helper functions '''


def find_index_of_selected_vertex(obj):

# force 'OBJECT' mode temporarily. [TODO]
selected_verts = [i.index for i in obj.data.vertices if i.select]

# prevent script from operating if currently >1 vertex is selected.
verts_selected = len(selected_verts)
if verts_selected != 1:
return None
else:
return selected_verts[0]



def find_connected_verts(obj, found_index):

edges = obj.data.edges
connecting_edges = [i for i in edges if found_index in i.vertices[:]]
if len(connecting_edges) != 2:
return None
else:
connected_verts = []
for edge in connecting_edges:
cvert = set(edge.vertices[:])
cvert.remove(found_index)
connected_verts.append(cvert.pop())

return connected_verts



def find_distances(obj, connected_verts, found_index):
edge_lengths = []
for vert in connected_verts:
co1 = obj.data.vertices[vert].co
co2 = obj.data.vertices[found_index].co
edge_lengths.append([vert, (co1-co2).length])
return edge_lengths



def generate_fillet(obj, c_index, max_rad, f_index):

def get_first_cut(outer_point, focal, distance_from_f):
co1 = obj.data.vertices[focal].co
co2 = obj.data.vertices[outer_point].co
real_length = (co1-co2).length
ratio = distance_from_f / real_length

# must use new variable, cannot do co1 += obj_center, changes in place.
new_co1 = co1 + obj_centre
new_co2 = co2 + obj_centre
return new_co1.lerp(new_co2, ratio)

obj_centre = obj.location
distance_from_f = max_rad * HALF_RAD

# make imaginary line between outerpoints
outer_points = []
for point in c_index:
outer_points.append(get_first_cut(point, f_index, distance_from_f))

# make imaginary line from focal point to halfway between outer_points
focal_coordinate = obj.data.vertices[f_index].co + obj_centre
center_of_outer_points = (outer_points[0] + outer_points[1]) / 2

# find radial center, by lerping ab -> ad
BC = (center_of_outer_points-outer_points[1]).length
AB = (focal_coordinate-center_of_outer_points).length
BD = (BC/AB)*BC
AD = AB + BD
ratio = AD / AB
radial_center = focal_coordinate.lerp(center_of_outer_points, ratio)

guide_line = [focal_coordinate, radial_center]
return outer_points, guide_line



def resposition_arc_points(arc_verts, radial_centre):
# ensure that every arc point is indeed radial distance away from center.
revised_arc_points = []

radial_dist_first = (arc_verts[0]-radial_centre).length
radial_dist_last = (arc_verts[-1]-radial_centre).length
desired_radial_distance = (radial_dist_first + radial_dist_last) * 0.5

for point in arc_verts:
radial_distance = (point-radial_centre).length
ratio = 1/(radial_distance / desired_radial_distance)
new_location = radial_centre.lerp(point, ratio)
new_distance = (radial_centre-new_location).length
revised_arc_points.append(new_location)
print("was", radial_distance, "becomes", new_distance)

return revised_arc_points



''' director function '''



def init_functions(self, context):

obj = context.object

# Finding vertex.
found_index = find_index_of_selected_vertex(obj)
if found_index != None:
print("you selected vertex with index", found_index)
connected_verts = find_connected_verts(obj, found_index)
else:
print("select one vertex, no more, no less")
return


# Find connected vertices.
if connected_verts == None:
print("vertex connected to only 1 other vert, or none at all")
print("remove doubles, the script operates on vertices with 2 edges")
return
else:
print(connected_verts)


# reaching this stage means the vertex has 2 connected vertices. good.
# Find distances and maximum radius.
distances = find_distances(obj, connected_verts, found_index)
for d in distances:
print("from", found_index, "to", d[0], "=", d[1])

max_rad = min(distances[0][1],distances[1][1])
print("max radius", max_rad)


return generate_fillet(obj, connected_verts, max_rad, found_index)



''' GL drawing '''



def draw_polyline_from_coordinates(context, points, LINE_TYPE):
region = context.region
rv3d = context.space_data.region_3d

bgl.glColor4f(1.0, 1.0, 1.0, 1.0)

if LINE_TYPE == "GL_LINE_STIPPLE":
bgl.glLineStipple(4, 0x5555)
bgl.glEnable(bgl.GL_LINE_STIPPLE)
bgl.glColor4f(0.3, 0.3, 0.3, 1.0)

bgl.glBegin(bgl.GL_LINE_STRIP)
for coord in points:
vector3d = (coord.x, coord.y, coord.z)
vector2d = loc3d2d(region, rv3d, vector3d)
bgl.glVertex2f(*vector2d)
bgl.glEnd()

if LINE_TYPE == "GL_LINE_STIPPLE":
bgl.glDisable(bgl.GL_LINE_STIPPLE)
bgl.glEnable(bgl.GL_BLEND) # back to uninterupted lines

return



def draw_points(context, points, size):
region = context.region
rv3d = context.space_data.region_3d


bgl.glEnable(bgl.GL_POINT_SMOOTH)
bgl.glPointSize(size)
# bgl.glEnable(bgl.GL_BLEND)
bgl.glBlendFunc(bgl.GL_SRC_ALPHA, bgl.GL_ONE_MINUS_SRC_ALPHA)

bgl.glBegin(bgl.GL_POINTS)
# draw red
bgl.glColor4f(1.0, 0.2, 0.2, 1.0)
for coord in points:
vector3d = (coord.x, coord.y, coord.z)
vector2d = loc3d2d(region, rv3d, vector3d)
bgl.glVertex2f(*vector2d)
bgl.glEnd()

bgl.glDisable(bgl.GL_POINT_SMOOTH)
bgl.glDisable(bgl.GL_POINTS)
return



def draw_callback_px(self, context):

objlist = context.selected_objects
names_of_empties = [i.name for i in objlist]

region = context.region
rv3d = context.space_data.region_3d
points, guide_verts = init_functions(self, context)

# draw bevel
draw_polyline_from_coordinates(context, points, "GL_LINE_STIPPLE")

# draw symmetry line
draw_polyline_from_coordinates(context, guide_verts, "GL_LINE_STIPPLE")

# get control points and knots.
h_control = guide_verts[0]
knot1, knot2 = points[0], points[1]
kappa_ctrl_1 = knot1.lerp(h_control, KAPPA)
kappa_ctrl_2 = knot2.lerp(h_control, KAPPA)
arc_verts = bezlerp(knot1, kappa_ctrl_1, kappa_ctrl_2, knot2, NUM_VERTS)

# draw fillet ( 2 modes )
if mode == 'TRIG':
radial_centre = guide_verts[1]
arc_verts = resposition_arc_points(arc_verts, radial_centre)
if mode == 'KAPPA':
print("using vanilla kappa, this mode produces a poor approximation")
pass

draw_polyline_from_coordinates(context, arc_verts, "GL_BLEND")

if DRAW_POINTS == True:
draw_points(context, arc_verts, 4.2)

# restore opengl defaults
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)
return



''' UI elements '''



class UIPanel(bpy.types.Panel):
bl_label = "Hello from UI panel"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"

scn = bpy.types.Scene
object = bpy.context.object
# scn.Monster = object.location.x
# scn.MyMove = bpy.props.FloatProperty()


def draw(self, context):
layout = self.layout
ob = context.object
scn = context.scene

row1 = layout.row(align=True)
# row1.prop(ob, "location")
row1.operator("dynamic.fillet")
# row1.prop(ob, 'location', index = 0, text = "Spine Spline", slider = True)




class OBJECT_OT_add_object(bpy.types.Operator):
bl_idname = "dynamic.fillet"
bl_label = "Check Vertice"
bl_description = "Allows the user to dynamically fillet a vert/edge"
bl_options = {'REGISTER', 'UNDO'}

'''
scale = FloatVectorProperty(name='scale',
default=(1.0, 1.0, 1.0),
subtype='TRANSLATION',
description='scaling')
'''


def modal(self, context, event):
context.area.tag_redraw()

if event.type == 'RIGHTMOUSE':
if event.value == 'RELEASE':
print("discontinue drawing")
context.area.tag_redraw()
context.region.callback_remove(self._handle)
return {'CANCELLED'}

return {'RUNNING_MODAL'}



def invoke(self, context, event):

if context.area.type == 'VIEW_3D':
context.area.tag_redraw()
context.window_manager.modal_handler_add(self)

# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
self._handle = context.region.callback_add(
draw_callback_px,
(self, context),
'POST_PIXEL')

return {'RUNNING_MODAL'}
else:
self.report({'WARNING'},
"View3D not found, cannot run operator")
context.area.tag_redraw()
return {'CANCELLED'}


'''

def execute(self, context):

#add_object(self, context)
init_functions(self, context)

return {'FINISHED'}

'''



bpy.utils.register_module(__name__)

July 23, 2011

Place empty while in Edit Mode

Sometimes we need to place a marker (Empty) at a given point on our mesh. I use this often to define a pivot point. But manually hopping in and out of edit mode to add an Empty soon becomes lame. This is my solution. Run this in edit mode, select as many verts as you wish.

July 21, 2011

scripted keyframing of hide / hide render setting on object

###### place at t = T_START-1 a keyframe with hide set to True
current_frame = T_START-1
bpy.ops.anim.change_frame(frame=current_frame)
bpy.context.active_object.hide = True
bpy.context.active_object.keyframe_insert(  data_path="hide", 
                                            index=-1, 
                                            frame=current_frame)    
    
###### place at T_START a keyframe with hide set to False
current_frame = T_START
bpy.ops.anim.change_frame(frame=current_frame)
bpy.context.active_object.hide = False
bpy.context.active_object.keyframe_insert(  data_path="hide", 
                                            index=-1, 
                                            frame=current_frame)
It's handy to make keyframes first as experiment for lateron when you try to script something, if you can't get the basics going manually don't expect to be able to script it without understanding the sequence of operations required.

If you have a property that you want to keyframe, then you will need to know the data_path. In the code example below i choose to keyframe the 'hide' property. If you are unsure about the string name needed for data_path, then rightclick the property/icon and select "copy data_path". Then paste that into ( console / TextEditor / your code ) to see the path.

if you need to remove all your animation data use action-data-how-to-purge.html

you might set up a hide animation like this
( side note: data_path='hide' hides from view, not from render.. that's data_path='hide_render' ).

# For instance you first define T_START, T_END and LAST_FRAME
# T_START and T_END as the first and last frames that the object will be visible in.
# Having an additional keyframe on either side of those frames allows you to play
# the sequence backwards without messing up the hide duration.  
# "hide" ( hides from view ) use "hide_render" to make it apply to render.
    time_and_state_settings = ( (0, True), 
                                (T_START-1, True),
                                (T_START, False),
                                (T_END, False),
                                (T_END+1, True),
                                (LAST_FRAME, True))

    for time_val in time_and_state_settings:    
        current_frame = time_val[0]
        bpy.ops.anim.change_frame(frame=current_frame)
        bpy.context.active_object.hide = time_val[1]
        bpy.context.active_object.keyframe_insert(  data_path="hide", 
                                                    index=-1, 
                                                    frame=current_frame)
    

July 18, 2011

basic calliper blender 2.5 (distance and angle)



Two modes to the plugin, this shows the 3 empty setup, it displays the angle between them. When you have two empties selected it displays the imaginary tetrahedron created by the bounding box of the 2 points in space.



code here : download


# it's over 700 lines of code i think, plenty of juicy examples

printing verts, face indices, and their normals

import bpy

current_obj = bpy.context.active_object

print("="*40) # printing marker
for polygon in current_obj.data.polygons:
    verts_in_face = polygon.vertices[:]
    print("face index", polygon.index)
    print("normal", polygon.normal)
    for vert in verts_in_face:
        print("vert", vert, " vert co", current_obj.data.vertices[vert].co)

July 16, 2011

Monster Tile Renderer v03 blender region rendering

v3 .005

This addon runs fine in blender 2.6x and has been updated. download here.
The manual is a wiki and can be viewed here



The UI looks something like this, depending on your theme.


July 15, 2011

filling clipboard in blender 2.5x (os independant)

i was shocked, amazed, confounded. and then i got over it :)

bpy.context.window_manager.clipboard = "some number"

this stores the string literal "some number" in your os clip board, can be used anywhere. (theoretically, but i only tried on linux)

July 13, 2011

OpenGL / bgl in blender 2.5

select two empties, run this code.

import bpy
import bgl
import blf
import bpy_extras

from mathutils import Vector
from bpy.props import StringProperty, FloatProperty
from bpy_extras import view3d_utils

'''
helper functions
'''

def get_objects(context):

sel_obs = context.selected_objects
names = [object.name for object in sel_obs if object.type=='EMPTY']
if len(names) == 2:
return names
else:
return None


def get_distance(names_of_empties):

if names_of_empties == None:
return 0.0

coordlist = []
for name in names_of_empties:
coordlist.append(bpy.data.objects[name].location)

return (coordlist[0]-coordlist[1]).length


def get_distance_from_context(context):
distance = get_distance(get_objects(context))
return distance


def get_coordinates_from_empties(object_list):
coordlist = [obj.location for obj in object_list]
return coordlist


def get_difference(axis, coord):

if axis == 'z':
return abs((coord[0]-coord[1]).z)
elif axis == 'y':
return abs((coord[0]-coord[1]).y)
elif axis == 'x':
return abs((coord[0]-coord[1]).x)
else:
return None


def return_sorted_coordlist(coords):
def MyFn(coord):
return coord.z
return sorted(coords, key=MyFn, reverse=True)


'''
openGL drawing
'''


def draw_text(col, y_pos, display_text, view_width, context):

# calculate text width, then draw
font_id = 0
blf.size(font_id, 18, 72) #fine tune

text_width, text_height = blf.dimensions(font_id, display_text)
right_align = view_width-text_width-18
blf.position(font_id, right_align, y_pos, 0)
blf.draw(font_id, display_text)
return


def draw_tetrahedron(region, rv3d, context, clist):

# highest point is apex
apex, baseco = return_sorted_coordlist(clist)

# define the base of the tetrahydron
base1 = Vector((apex.x, apex.y, baseco.z))
base2 = Vector((apex.x, baseco.y, baseco.z))
base3 = baseco

# converting to screen coordinates
screen_apex = view3d_utils.location_3d_to_region_2d(region, rv3d, apex)
screen_base1 = view3d_utils.location_3d_to_region_2d(region, rv3d, base1)
screen_base2 = view3d_utils.location_3d_to_region_2d(region, rv3d, base2)
screen_base3 = view3d_utils.location_3d_to_region_2d(region, rv3d, base3)

# bgl.glBegin(bgl.GL_LINE)

# colour + line setup, 50% alpha, 1 px width line
bgl.glEnable(bgl.GL_BLEND)
bgl.glColor4f(0.1, 0.3, 1.0, 0.8)
bgl.glLineWidth(1)

# from top to base coordinates
bgl.glColor4f(0.6, 0.6, 0.6, 0.8)
bgl.glBegin(bgl.GL_LINES)
bgl.glVertex2f(*screen_apex)
bgl.glVertex2f(*screen_base3)
bgl.glEnd()

bgl.glColor4f(0.1, 0.3, 1.0, 0.8)
bgl.glBegin(bgl.GL_LINES)
bgl.glVertex2f(*screen_apex)
bgl.glVertex2f(*screen_base1)
bgl.glEnd()

bgl.glColor4f(0.1, 0.3, 1.0, 0.2)
bgl.glBegin(bgl.GL_LINES)
bgl.glVertex2f(*screen_apex)
bgl.glVertex2f(*screen_base2)
bgl.glEnd()

# between base coordinates
bgl.glColor4f(1.0, 0.1, 0.1, 0.8)
bgl.glBegin(bgl.GL_LINES)
bgl.glVertex2f(*screen_base3)
bgl.glVertex2f(*screen_base2)
bgl.glEnd()

bgl.glColor4f(0.0, 1.0, 0.1, 0.8)
bgl.glBegin(bgl.GL_LINES)
bgl.glVertex2f(*screen_base2)
bgl.glVertex2f(*screen_base1)
bgl.glEnd()

bgl.glColor4f(0.1, 0.3, 1.0, 0.2)
bgl.glBegin(bgl.GL_LINES)
bgl.glVertex2f(*screen_base1)
bgl.glVertex2f(*screen_base3)
bgl.glEnd()


return


def draw_callback_px(self, context):
rounding = 6

objlist = context.selected_objects
names_of_empties = [i.name for i in objlist]
distance_value = get_distance(names_of_empties)
coordinate_list = get_coordinates_from_empties(objlist)

region = bpy.context.region
rv3d = bpy.context.space_data.region_3d
view_width = context.region.width

# major rewrite candidate
l_distance = str(round(distance_value, rounding))
x_distance = round(get_difference('x', coordinate_list),rounding)
y_distance = round(get_difference('y', coordinate_list),rounding)
z_distance = round(get_difference('z', coordinate_list),rounding)
l_distance = str(l_distance)+" lin"
x_distance = str(x_distance)+" x"
y_distance = str(y_distance)+" y"
z_distance = str(z_distance)+" z"

y_heights = 88, 68, 48, 20
y_heights = [m-9 for m in y_heights] # fine tune

str_dist = x_distance, y_distance, z_distance, l_distance
for i in range(len(y_heights)):
draw_text(True, y_heights[i], str_dist[i], view_width, context)

# 50% alpha, 2 pixel width line
bgl.glEnable(bgl.GL_BLEND)

bgl.glColor4f(0.7, 0.7, 0.7, 0.5)
bgl.glLineWidth(1)

bgl.glBegin(bgl.GL_LINE_STRIP)
for coord in coordinate_list:
vector3d = (coord.x, coord.y, coord.z)
vector2d = view3d_utils.location_3d_to_region_2d(region, rv3d, vector3d)
bgl.glVertex2f(*vector2d)
bgl.glEnd()

draw_tetrahedron(region, rv3d, context, coordinate_list)

# restore opengl defaults
bgl.glLineWidth(1)
bgl.glDisable(bgl.GL_BLEND)
bgl.glColor4f(0.0, 0.0, 0.0, 1.0)



'''
tool panel and button definitions
'''


class ToolPropsPanel(bpy.types.Panel):
bl_label = "Empties Calliper"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"

scn = bpy.types.Scene
ctx = bpy.context

@classmethod
def poll(self, context):
names_of_empties = get_objects(context)
if names_of_empties != None:
return True

def draw(self, context):

display_distance_field = False

layout = self.layout
scn = context.scene

names_of_empties = get_objects(context)
names_str = str(names_of_empties)

if names_of_empties != None:
display_distance_field = True
distance_value = get_distance(names_of_empties)


distance_value = str(distance_value)

# drawing
row1 = layout.row(align=True)
row1.operator("hello.hello", text=names_str)

if display_distance_field == True:
row3 = layout.row(align=True)
row3.label(distance_value)


class OBJECT_OT_HelloButton(bpy.types.Operator):
bl_idname = "hello.hello"
bl_label = "Say Hello"

def modal(self, context, event):
context.area.tag_redraw()

if event.type == 'MOUSEMOVE':
print("mouse moved")
elif event.type in ('RIGHTMOUSE', 'ESC'):
context.region.callback_remove(self._handle)
return {'CANCELLED'}

return {'RUNNING_MODAL'}

def invoke(self, context, event):
if context.area.type == 'VIEW_3D':
context.window_manager.modal_handler_add(self)

# Add the region OpenGL drawing callback
# draw in view space with 'POST_VIEW' and 'PRE_VIEW'
self._handle = context.region.callback_add(
draw_callback_px,
(self, context),
'POST_PIXEL')

return {'RUNNING_MODAL'}
else:
self.report({'WARNING'},
"View3D not found, cannot run operator")

return {'CANCELLED'}



bpy.utils.register_module(__name__)



and

latest version (wed 13 july 2011):

i'll want something like

July 12, 2011

2k lines of code on the blog.

2181 lines of code today.

calculating px and DPI information


# GPL 2 License 2011 Dealga McArdle July 12

import math

CMINCH = 0.393700787 # 1 centimeter = 0.393700787 inch
INCHCM = 2.54 # 1 inch = 2.54 centimeters

# or whatever your machine can handle comfortably
largest_tile_dimension_wide = 1000
largest_tile_dimension_high = 1000

def get_pixels(mode, width, height, DPI):
'''
Takes realworld width, height and DPI to produce raster equivalent

arguments : Description
------------------------------------------------------------
mode : (string) ['CM','INCH'], prepares functions for input values
width : (float) or int value for the real world measurement
height : (float) or int value for the real world measurement
DPI : (int) to declare what DPI you are aiming for.

returns : by valid input:
cells_wide, cells_high, total_px_width, total_px_height

by invalid input: returns None
'''

if mode == 'CM':
width_in_inches = width*CMINCH
height_in_inches = height*CMINCH
elif mode == 'INCH':
width_in_inches = width
height_in_inches = height
else:
print("mode must be 'INCH' or 'CM'")
return None

print("width", width, "x height", height, mode, "| DPI", DPI)
if mode == 'CM':
print("width", width_in_inches,"(inches)")
print("height", height_in_inches, "(inches)")

print("\nat", DPI, "DPI that gives: ")

# [ ] todo verify if rounding up here is cool
w_in_px = width_in_inches*DPI
h_in_px = height_in_inches*DPI
w_in_px = math.floor(w_in_px)
h_in_px = math.floor(h_in_px)

print("Width", w_in_px,"px. Height", h_in_px, "px")

# determine number of tiles wide / high
cells_wide = math.ceil(w_in_px / largest_tile_dimension_wide)
cells_high = math.ceil(h_in_px / largest_tile_dimension_high)
px_wide_per_tile = math.floor(w_in_px / cells_wide)
px_high_per_tile = math.floor(h_in_px / cells_high)

print(cells_wide, "tiles wide, at", px_wide_per_tile, "px wide")
print(cells_high, "tiles high, at", px_high_per_tile, "px high")
return cells_wide, cells_high, w_in_px, h_in_px

get_pixels('INCH', 100, 240, 300)


July 11, 2011

getting index of currently selected vertex ( or vertices)

this tells you the index of the currently selected vertex (or if more than one, then it returns the list of selected vertices)
# you must be in 'OBJECT' mode to run these.
>>> [i.index for i in bpy.context.active_object.data.vertices if i.select == True]
[5]
# or slightly shorter, when i.select returns False the element is discarded,
# but when i.select returns True, the if statement compares True with True. (overkill)
>>> [i.index for i in bpy.context.active_object.data.vertices if i.select]
[5]
# or use shorthand to access active_object (more pythonic ):
>>> current_object = bpy.context.active_object
>>> [i.index for i in current_object.data.vertices if i.select]
[5]

# to get a coordinate: (will return all selected vertices if more than one selected)
>>> [i.co for i in bpy.context.active_object.data.vertices if i.select]
[Vector((2.4389050006866455, -3.9095726013183594, 0.9682117700576782))]
Here is a similar snippet that works with bmesh and updates without requiring to enter/exit edit mode.

What about Faces?

Please see this post, it shows two ways.

Text Object (bpy.data.texts) operations

with a textobject with text do

>>>bpy.data.texts[0].lines[0].body
...this is the first line of your text object
so if you want the lines between some selection

>>> [line.body for line in bpy.data.texts[0].lines[4:8]]
['line 5', 'line 6', 'line 7', 'line 8']

>>> selection = [line.body for line in bpy.data.texts[0].lines[4:8]]
>>> for line in selection: print(line)
...
line 5
line 6
line 7
line 8

Selecting Edges using some criteria

Below are two methods to select parts of geometry. Sometimes you might want to operate on geometry and don't require user interaction (ie you may not want to be in 'edit mode'). In such cases you might use code similar to the snippet below. to select or unselect edged using bpy.ops you need to be in 'OBJECT' mode. some code to clarify:
import bpy
# this will select every 'even' numbered edge from the list of edges.

# while in object mode, with no edges selected in the underlying object
obj = bpy.context.active_object

bpy.ops.object.mode_set(mode='EDIT')
bpy.context.tool_settings.mesh_select_mode = (False, True, False) # force edges
bpy.ops.mesh.delete(type='ONLY_FACE')

bpy.ops.object.mode_set(mode='OBJECT')

for idx, edge in enumerate(obj.data.edges):
    if (idx % 2) == 0:
        print("selecting", idx)
        edge.select = True
        continue
    
    edge.select = False
        
bpy.ops.object.editmode_toggle()
But sometimes you need to be able to get information about the geometry of an object while in edit mode, and the most convenient way to do that is sing BMesh. The TextEditor templates now has a BMesh demo script which is very interesting for newcomers.
# uses bmesh
# be in edit mode, (edge select mode will be forced)
# any already selected edges will be unselected if they have uneven index.

import bpy
import bmesh

# force edge selection mode
bpy.context.tool_settings.mesh_select_mode = (False, True, False) 

# this will select every 'even' numbered edge from the list of edges.
obj = bpy.context.active_object

bm = bmesh.from_edit_mesh(obj.data)
for edge in bm.edges:
    if edge.index % 2 == 0:
        edge.select = True
        continue
    
    edge.select = False
    



you can think of all kinds of criteria to determine if you want to select an edge (similar technique applies for faces and verts)

July 10, 2011

Areas

if this is a short post, come back later. or next week. when i have more to write about it.
>>> for area in bpy.context.window.screen.areas: print(area.type)
...
INFO
PROPERTIES
OUTLINER
VIEW_3D
TEXT_EDITOR
CONSOLE

>>> bpy.context.window.screen.areas[3].regions[2].type
'TOOL_PROPS'
it is possible to query regions in that area (header, toolshelves..etc)
>>>bpy.context.area.regions[0].type
...'Header'


# good
bpy.context.area.type = 'GRAPH_EDITOR'

# bad, will crash blender until fixed
bpy.context = 'GRAPH_EDITOR'

July 09, 2011

Monster Renderer (tile / region rendering)

EDIT:
i have since updated this script. read about it

here




monster_tile_renderer
monster_tile_renderer_2

usage note:
- filename won't update until you restart blender (Working on it! You can still modify by hand)

installation note:
- place monster_tile_renderer_2.py in your scripts/addons directory
- the addon will appear in the 'render' addons list.

autostitching
for autostitching i wrote a tool, it uses and older version of Python 2.6/7 because PIL image library is used for stitching. Auto_Stitcher Download.py
# GPL2 license - code by Dealga McArdle (zeffii) 2011 july 12
'''
usage, 
1) stick the output of Monster Tile Renderer in a folder.
2) set the path variable to that folder  ('..fullpath/stitching/files/')
3) run the script.

'''


# stitching your images, get py 2.6/2.7 and get PIL
import os
import re
import PIL
from PIL import Image

output_format = 'PNG'
path = '/home/zeffii/stitching/files2/'
os.chdir(path) # set this folder active

mycurdir = os.getcwdu()
filelist = os.listdir(mycurdir) # lists content (supposed to be images only!)
filelist = sorted(filelist)

stitchlist = []
for i in filelist:
    strname = str(i[:])
    filepath = path+strname
    
    # disect filename, get dimensions
    db = Image.open(filepath)
    match = re.search('\_(\d+\_\d+)\.', filepath)
    
    match_str = ""
    if match.group() != None:
        match_str = match.group(1)
    else:
        print("use filenames like yourfilname_col_row.ext")
        print("then, if still issues, check the directory for uncommon characters")
        break

    col_row = tuple(match_str.split("_"))
    col_row = [int(dimension) for dimension in col_row]
    
    # store as tuple, tuple, string
    stitch_up = (tuple(col_row), db.size, filepath)
    stitchlist.append(stitch_up)

# stitchlist items are ((column,row),(x, y), '/path/full_including_extension')
rows = stitchlist[-1][0][0]
columns = stitchlist[-1][0][1]

# there has to be neater way to generate a multidimensional list with elements 
# that don't all point to the same place in memory
main_matrix = []
for i in range(rows):
    minor_matrix = []
    for m in range(columns):
        minor_matrix.append([])
    main_matrix.append(minor_matrix)

# temporary
for entry in stitchlist: 
    main_matrix[entry[0][0]-1][entry[0][1]-1] = entry[1]
    
# get composite dimensions
px_wide = sum([i[0][0] for i in main_matrix])
px_high = sum([i[1] for i in main_matrix[0]])
print("px_wide", px_wide, " px_high", px_high)

# permanent
for entry in stitchlist: 
    main_matrix[entry[0][0]-1][entry[0][1]-1] = entry

comp_image = Image.new('RGB', (px_wide, px_high))

ypos = 0
xpos = 0
current_height = 0
current_width = 0
for col in main_matrix:
    ypos = 0
    #do top to bottom
    for row in col:
        ymp = Image.open(row[2])
        current_width = row[1][0]
        current_height = row[1][1]
        ymp = ymp.crop((0,0,current_width, current_height))
        comp_image.paste(ymp, (xpos,ypos))
        ypos += current_height
    xpos += current_width

comp_image.show()
comp_image.save(path+"composited.png", format=output_format)

July 08, 2011

creating a mesh using primitives, polysphere


import bpy
import math

# constant
r90 = math.radians(90)

# variables
subdiv = 7

# add an new MESH object, place it in 'EDIT' mode
bpy.ops.object.add(type='MESH')
bpy.context.object.name = 'PolySphere'
bpy.ops.object.mode_set(mode='EDIT')

# define locations and rotations of the mesh grid objects
loc_list = (0, 0, 1), (0, 0, -1), (1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, -1, 0)
rot_list = (0, 0, 0),(0, r90*2, 0),(0, r90, 0),(0, -r90, 0),(-r90, 0, 0), (r90, 0, 0)

# add 6 faces
for i in range(6):
bpy.ops.mesh.primitive_grid_add( x_subdivisions=subdiv,
y_subdivisions=subdiv,
size=1,
view_align=False,
enter_editmode=True,
location=loc_list[i],
rotation=rot_list[i])

# select all verts, and remove doubles, end up with neat cube
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.select_all(action='TOGGLE')
bpy.ops.mesh.remove_doubles(limit=0.0001)

# set
bpy.ops.transform.tosphere(value=1)
bpy.ops.mesh.vertices_smooth(repeat=20, xaxis=True, yaxis=True, zaxis=True)
bpy.ops.transform.tosphere(value=1)

Tile Renderer (Make Big Renders)

This post was written quite some time before I completed a decent version of the tile renderer. Monster Tile Renderer - for more information, or search this blog or checkout the github page
import bpy
# tile rendering 0.1
# Dealga McArdle (zeffii) 2011-July 8.

# fill in how many tiles you want, must be non zero.
n = 2 # n tiles wide (x)
m = 2 # m tiles high (y)
percentage_of_current = 300
total_tiles = n*m
location = "/home/zeffii/"
filename = "MyRender"
filetype = "png"


name_list = []
for j in range(m):
    for i in range(n):
        name_list.append(str(i+1)+"_"+str(j+1))

border_list = []
wslice = 1/n
hslice = 1/m

# use border and crop
bpy.context.scene.render.use_border = True
bpy.context.scene.render.use_crop_to_border = True

# set resolution percentage
bpy.context.scene.render.resolution_percentage = percentage_of_current

for cell in name_list:
    wdiv, hdiv = cell.split("_")
    bmin_x = float((int(wdiv)-1)*wslice)
    bmax_x = float(int(wdiv)*wslice)
    bmin_y = 1.0 - (float(int(hdiv)*hslice))
    bmax_y = 1.0 - (float((int(hdiv)-1)*hslice))
        
    str_borders = ("bmin_x="+str(bmin_x),
                   "bmax_x="+str(bmax_x),
                   "bmin_y="+str(bmin_y),
                   "bmax_y="+str(bmax_y))
                   
    border_list.append((cell,str_borders))

    # set border for this tile
    bpy.context.scene.render.border_min_x = bmin_x
    bpy.context.scene.render.border_max_x = bmax_x
    bpy.context.scene.render.border_min_y = bmin_y
    bpy.context.scene.render.border_max_y = bmax_y 
    
    bpy.ops.render.render()
    RR = "Render Result"
    bpy.data.images[RR].save_render(location+filename+"_"+cell+"."+filetype)
    print("rendered", cell)

July 07, 2011

How to set active_object via python

a simple one liner!
"""using an object reference"""
>>> bpy.context.scene.objects.active = bpy.data.objects["Cube"]

# if checked to see which is now active
>>> bpy.context.scene.objects.active

# this may not update visibly as you might expect in the viewport, 
# but you will see the origin of active_object, and the name bottom left.
bpy.data.objects['Cube']

active != selected, you could have 10 objects selected, of which only 1 can ever be active at a time. Or none selected, but 1 active. To get a better understanding of this not so intuitive situation: unselect everything, if you have two objects in the scene, the last one interacted with will be 'active' (as in, shows the origin, and name displayed bottom left)

Setting the state of an object to 'select = True' , is not the same thing

Example usage of active object

as an example, below is code that adds Empties while an object is in Edit Mode, the script must set the active object back to the former object, after an empty is added. Try it with suzanne in Edit Mode, then select one vertex and run the script.

July 06, 2011

Creating a Group with no objects

bpy.data.groups.new("alom")
won't appear in the outliner until it has objects associated with it.

Hide_select, marking multiple objects to be unselectable


# with numerous objects selected, if you want to prevent their selection 'en masse'
for i in bpy.context.selected_objects:
i.hide_select = True

you might be interested in .hide and .hide_render

Selecting ungrouped objects


# a little verbose? :)
import bpy

scene_groups = bpy.data.groups

# make sure before this, that nothing is selected.
names_of_grouped_objects = []

for i in bpy.context.selectable_objects:
for group in bpy.data.groups:
for item in group.objects:
if item.name == i.name:
print("i.name", i.name, " is grouped in ", group.name)
names_of_grouped_objects.append(i.name)

# ungrouped objects
print("ungrouped objects are these")
for i in bpy.context.selectable_objects:
if i.name not in names_of_grouped_objects:
print(">>>",i.name)
bpy.data.objects[i.name].select = True

July 05, 2011

printing verts and faces of object to a text file

July 04, 2011

Making a cube using from_pydata

Some primitives, generally geometry is built using triangles (tris) or quadrangles (quads)

Quads

import bpy

verts = [(1.0, 1.0, -1.0),
         (1.0, -1.0, -1.0),
        (-1.0, -1.0, -1.0),
        (-1.0, 1.0, -1.0),
         (1.0, 1.0, 1.0),
         (1.0, -1.0, 1.0),
        (-1.0, -1.0, 1.0),
        (-1.0, 1.0, 1.0)]

faces = [(0, 1, 2, 3),
         (4, 7, 6, 5),
         (0, 4, 5, 1),
         (1, 5, 6, 2),
         (2, 6, 7, 3),
         (4, 0, 3, 7)]

mesh_data = bpy.data.meshes.new("cube_mesh_data")
mesh_data.from_pydata(verts, [], faces)
mesh_data.update() # (calc_edges=True) not needed here

cube_object = bpy.data.objects.new("Cube_Object", mesh_data)

scene = bpy.context.scene  
scene.objects.link(cube_object)  
cube_object.select = True  

Tris

verts = [
(-0.285437,-0.744976,-0.471429),
(-0.285437,-0.744976,-2.471429),
(1.714563,-0.744976,-2.471429),
(1.714563,-0.744976,-0.471429),
(-0.285437,1.255024,-0.471429),
(-0.285437,1.255024,-2.471429),
(1.714563,1.255024,-2.471429),
(1.714563,1.255024,-0.471429)]

faces =  [
(4,5,1),
(5,6,2),
(6,7,3),
(4,0,7),
(0,1,2),
(7,6,5),
(0,4,1),
(1,5,2),
(2,6,3),
(7,0,3),
(3,0,2),
(4,7,5)]

import bpy  
  
mesh_data = bpy.data.meshes.new("cube_mesh_data")  
mesh_data.from_pydata(verts, [], faces)  
mesh_data.update() # (calc_edges=True) not needed here  
  
cube_object = bpy.data.objects.new("Cube_Object", mesh_data)  
  
scene = bpy.context.scene    
scene.objects.link(cube_object)    
cube_object.select = True    

bgl drawing with OpenGL onto blender 2.5 view

original thread over at blenderartists.com, i never finished it but the bgl is decent enough to get someone started.
'''
This script resects the camera position into virtual 3d space.
Dealga McArdle (c) 2011

The program may be distributed under the terms of the GNU General
Public License. The full terms of GNU GPL2 can be found at: 
http://www.gnu.org/licenses/gpl-2.0.html

Be sure that you understand the GLP2 terms prior to using this script.
Nothing between these tripple quote marks should be construed as
having deminished the full extent of the GPL2 license.
'''

import bpy
import bgl
import blf
from mathutils.geometry import intersect_line_line
from mathutils import Vector

'''
- TODO: complete vanishing point and horizon drawing
- TODO: correctly deal with impossible guides orientations
- TODO: implement rudimentary double buffer for 3d openGL drawing
'''


''' defining globals '''

# initial end point locations, lateron modified by the user.
guide_green_1 = [50, 100, 280, 120]
guide_green_2 = [50, 70, 280, 60]
guide_red_1 = [300, 120, 580, 100]
guide_red_2 = [300, 30, 580, 70]
guide_blue = [250, 50, 250, 250]
h_collection = [guide_green_1, guide_green_2, guide_red_1, guide_red_2, guide_blue]

# colours defined here for scope
line_green = 0.0, 1.0, 0.0, 0.4
line_red = 1.0, 0.0, 0.0, 0.4
line_blue = 0.0, 0.0, 1.0, 0.4
l_col_green = 0.5, 1.0, 0.5, 0.6
l_col_red = 1.0, 0.3, 0.3, 0.6
l_col_cyan = 0.6, 0.6, 1.0, 0.4

# handle size is double this value
hSize = 5

# colours/transparency of viewport text
dimension_colour = (1.0, 1.0, 1.0, 1.0)
explanation_colour = (1.0, 1.0, 1.0, 0.7)


''' G L  D R A W I N G '''


def drawOneLine(x1, y1, x2, y2, colour):
    '''accepts 2 coordinates and a colour then draws
    the line and the handle'''

    def DrawHandle(hX, hY):
        bgl.glBegin(bgl.GL_LINE_LOOP)
        bgl.glVertex2i(hX+hSize, hY-hSize)
        bgl.glVertex2i(hX-hSize, hY-hSize)
        bgl.glVertex2i(hX-hSize, hY+hSize)
        bgl.glVertex2i(hX+hSize, hY+hSize)
        bgl.glEnd()

    #set colour to use
    bgl.glColor4f(*colour)

    #draw main line and handles
    bgl.glBegin(bgl.GL_LINES)
    bgl.glVertex2i(x1,y1)
    bgl.glVertex2i(x2,y2)
    bgl.glEnd()
    DrawHandle(x1, y1)
    DrawHandle(x2, y2)


def DrawOrientationLines():
    '''configure and initialize the 5 orientation lines
    drawOneLine(x1, y1, x2, y2, colour)'''
    drawOneLine(*guide_green_1, colour=line_green)  #green
    drawOneLine(*guide_green_2, colour=line_green)
    drawOneLine(*guide_red_1, colour=line_red)  #red
    drawOneLine(*guide_red_2, colour=line_red)
    drawOneLine(*guide_blue, colour=line_blue)  #blue


def DrawPerspectiveLine(x1, y1, x2, y2, l_colour):
    '''reckon could be refactored with DrawOneLine
    but i'm considering giving these lines dashed style'''
    bgl.glColor4f(*l_colour)
    bgl.glBegin(bgl.GL_LINES)
    bgl.glVertex2i(x1,y1)
    bgl.glVertex2i(x2,y2)
    bgl.glEnd()


def IntersectionOf(line1, line2):
    '''mathutils.geometry expects lines to be expressed as two
    Vectors with three dimensions, at this point we pick an
    arbitrary value for the z component of this vector.
    I'm only interested in how the two guides extend towards
    a vanishing point, and the resulting x,y coordinate.'''
    arbitrary_z_value = 0.0
    A = Vector((line1[0], line1[1], arbitrary_z_value))
    B = Vector((line1[2], line1[3], arbitrary_z_value))
    C = Vector((line2[0], line2[1], arbitrary_z_value))
    D = Vector((line2[2], line2[3], arbitrary_z_value))
    my_xyz = intersect_line_line(A, B, C, D)
    if my_xyz == None: return 10,40
    return int(my_xyz[0][0]), int(my_xyz[0][1])


def DrawHorizonAndVanishingPoints():
    '''use the current state of the guide coordinates, to draw:
    - both vanishing points, O and R
    - all 4 guide ends (M,N,P,Q)'''
    # setting up extra drawing points/lines.
    p_point_o = IntersectionOf(guide_green_1, guide_green_2)
    p_point_r = IntersectionOf(guide_red_1, guide_red_2)

    p_line_mo = p_point_o[0], p_point_o[1], h_collection[0][0], h_collection[0][1]
    p_line_no = p_point_o[0], p_point_o[1], h_collection[1][0], h_collection[1][1]
    p_line_pr = p_point_r[0], p_point_r[1], h_collection[2][2], h_collection[2][3]
    p_line_qr = p_point_r[0], p_point_r[1], h_collection[3][2], h_collection[3][3]
    h_line_or = p_point_o[0], p_point_o[1], p_point_r[0], p_point_r[1]
    
    # draw the resulting perspective lines and horizon.
    DrawPerspectiveLine(*p_line_mo, l_colour = l_col_green) # green
    DrawPerspectiveLine(*p_line_no, l_colour = l_col_green) 
    DrawPerspectiveLine(*p_line_pr, l_colour = l_col_red) # red
    DrawPerspectiveLine(*p_line_qr, l_colour = l_col_red)
    DrawPerspectiveLine(*h_line_or, l_colour = l_col_cyan) # cyan - horizon
    return


def DrawGeneratedAxis():
    '''for drawing the dashed line to indicate major axis'''
    return


def DrawStringToViewport(my_string, pos_x, pos_y, size, colour_type):
    ''' my_string : the text we want to print
        pos_x, pos_y : coordinates in integer values
        size : font height.
        colour_type : used for definining the colour'''
    my_dpi, font_id = 72, 0 # dirty fast assignment
    bgl.glColor4f(*colour_type)
    blf.position(font_id, pos_x, pos_y, 0)
    blf.size(font_id, size, my_dpi)
    blf.draw(font_id, my_string)


def InitViewportText(self, context):
    '''used to deligate opengl text printing to the viewport'''
    this_h = context.region.height
    this_w = context.region.width
    dimension_string = "IMAGE_EDITOR: H " + str(this_h) + " / W " + str(this_w)
    explanation_string = "right-click to release the script, data will be stored"
    DrawStringToViewport(dimension_string, 10, 20, 20, dimension_colour)
    DrawStringToViewport(explanation_string, 10, 7, 10, explanation_colour)


def InitGLOverlay(self, context):
    InitViewportText(self, context)

    # 50% alpha, 2 pixel width line
    bgl.glEnable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 0.5)
    bgl.glLineWidth(1.5)

    # start visible drawing
    DrawHorizonAndVanishingPoints()
    DrawOrientationLines()
    ## DrawGeneratedAxis()
    ## DrawGrid()

    # restore opengl defaults
    bgl.glLineWidth(1)
    bgl.glDisable(bgl.GL_BLEND)
    bgl.glColor4f(0.0, 0.0, 0.0, 1.0)


''' H A N D L E  C H E C K I N G '''


def CheckIsCursorOnPickPoints(event):
    '''CheckIsCursorOnPickPoints is not a prime example of HiQ code,
    but for now it does what i need, and identifies what handle will
    be modified by the drag, 0 = first handle, 2= second handle'''

    def FindGuideIndex(coordinates):
        for coord_iterator in range(len(h_collection)):
            if coordinates == h_collection[coord_iterator]:
                return coord_iterator

    def CheckHandle(guide, g_handle):
        if cmX >= (guide[g_handle]-hSize):
            if cmX <= (guide[g_handle]+hSize):
                if cmY >= (guide[g_handle+1]-hSize):
                    if cmY <= (guide[g_handle+1]+hSize):
                        return True
        else: return False

    def IsOnHandle(h_collection, cmX, cmY):
        for guide in h_collection:
            if CheckHandle(guide, 0): return (guide, 0)
            elif CheckHandle(guide, 2): return (guide, 2)
        return 'None'

    cmX, cmY = event.mouse_region_x, event.mouse_region_y
    is_on_handle_response = IsOnHandle(h_collection, cmX, cmY)
    if is_on_handle_response == 'None':
        return('None')
    else:
        handle_coordinates, handle_num = is_on_handle_response
        guide_index = FindGuideIndex(handle_coordinates)
        return(guide_index, handle_num)



class CameraMatchingPanel(bpy.types.Panel):
    bl_label = "Camera Matching"
    bl_space_type = "IMAGE_EDITOR"
    bl_region_type = "UI"

    def draw(self, context):
        layout = self.layout
        layout.label("Blue: nearest vertical")
        layout.label("Red/Green: perpendicular lines")
        layout.separator()

        layout = self.layout
        layout.label("Draw Perspective Lines")
        row = layout.row(align=True)
        row.operator("object.button", icon="MANIPUL")

        layout = self.layout
        layout.label("Solve Camera Location")
        row = layout.row(align=True)
        row.operator("object.button2", icon='SCENE')


class OBJECT_OT_Button(bpy.types.Operator):
    bl_idname = "object.button"
    bl_label = "Enable"

    def modal(self, context, event):
        context.area.tag_redraw()

        if event.type == 'LEFTMOUSE':
            if event.value == 'PRESS':
                self.cursor_on_handle = CheckIsCursorOnPickPoints(event)
                if self.cursor_on_handle == 'None': print("no handle associated")
                else: print(self.cursor_on_handle)
            if event.value == 'RELEASE':
                self.cursor_on_handle = 'None'

        if event.type == 'MOUSEMOVE' and self.cursor_on_handle != 'None':
            print("mouse moving x", event.mouse_region_x,"y", event.mouse_region_y)
            global h_collection
            h_collection[self.cursor_on_handle[0]][self.cursor_on_handle[1]] = \   
                                                            event.mouse_region_x
            h_collection[self.cursor_on_handle[0]][self.cursor_on_handle[1]+1] = \                                                                 
                                                            event.mouse_region_y

        if event.type in ('RIGHTMOUSE', 'ESC'):
            context.region.callback_remove(self._handle)
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        if context.area.type == 'IMAGE_EDITOR':
            self.cursor_on_handle = 'None'
            context.window_manager.modal_handler_add(self)

            # Add the region OpenGL drawing callback
            # draw in view space with 'POST_VIEW' and 'PRE_VIEW'
            PP = 'POST_PIXEL'
            dest = (self, context)
            self._handle = context.region.callback_add(InitGLOverlay, dest, PP)
            return {'RUNNING_MODAL'}
        else:
            self.report({'WARNING'}, "Image View not found, cannot run operator")
            return {'CANCELLED'}


class OBJECT_OT_Button2(bpy.types.Operator):
    bl_idname = "object.button2"
    bl_label = "Place Camera and Empty"

    def execute(self, context):
        print("Hello camera")
        return{'FINISHED'}

def register():
    bpy.utils.register_class(OBJECT_OT_Button)
    bpy.utils.register_class(OBJECT_OT_Button2)
    bpy.utils.register_class(CameraMatchingPanel)

def unregister():
    bpy.utils.unregister_class(OBJECT_OT_Button)
    bpy.utils.unregister_class(OBJECT_OT_Button2)
    bpy.utils.unregister_class(CameraMatchingPanel)

if __name__ == "__main__":
    register()

July 03, 2011

List Comprehension Quick example


>>> m = [["sta",[2,1]],["stab",[22,11]],["stam",[24,14]]]
>>> en = [f[1] for f in m]
>>> en
[[2, 1], [22, 11], [24, 14]]

Sorting Edge keys Part II

mostly rewritten, this is currently prototype so clunky/verbose code. but works ! :) assumes the polyline (edgebased) object is
1) not closed,
2) not interupted,
3) not already correctly sorted



import bpy

print("\n")
print("="*50)

cobject = bpy.context.active_object

idx_vert_list = []
for i in cobject.data.vertices:
idx_vert_list.append([i.index, i.co])
# print(i.co, i.index)

for i in idx_vert_list:
print(i)


# existing edges
print("=== +")
ex_edges = []
existing_edges = []
for i in cobject.data.edges:
edge_keys = [i.vertices[0], i.vertices[1]]
ex_edges.append(edge_keys)
item = [i.index, edge_keys]
existing_edges.append(item)
print(item)


# proposed edges
print(" becomes")
proposed_edges = []
num_edges = len(existing_edges)
for i in range(num_edges):
item2 = [i,[i,i+1]]
proposed_edges.append(item2)
print(item2)


# find first end point, discontinue after finding a lose end.
current_sequence = []
iteration = 0
while(iteration <= num_edges):
count_presence = 0
for i in existing_edges:
if iteration in i[1]:
count_presence += 1

print("iteration: ", iteration, count_presence)
if count_presence == 1:
break
iteration += 1

init_num = iteration
print("end point", init_num)


# find connected sequence
seq_list = []
glist = []

def generate_ladder(starter, edge_key_list):

def find_vert_connected(vert, mlist):
if len(mlist) == 1:
for g in mlist:
for k in g:
if k is not vert:
return(k, -1)

for i in mlist:
if vert in i:
idx = mlist.index(i)
for m in i:
if m is not vert:
return(m, idx)

stairs = []
while(True):
stairs.append(starter)
starter, idx = find_vert_connected(starter, edge_key_list)
if idx == -1:
stairs.append(starter)
break
edge_key_list.pop(idx)
return(stairs)

seq_list = generate_ladder(init_num, ex_edges)


# make verts and edges
Verts = []
Edges = []

for i in range(len(idx_vert_list)):
print(i)
old_idx = seq_list[i]
myVec = idx_vert_list[old_idx][1]
Verts.append((myVec.x, myVec.y, myVec.z))

for i in Verts: print(i)

for i in proposed_edges:
Edges.append(tuple(i[1]))
print(Edges)

bpy.ops.object.mode_set(mode = 'OBJECT')

prof_mesh = bpy.data.meshes.new("test_mesh2")
prof_mesh.from_pydata(Verts, Edges, [])
prof_mesh.update()
cobject.data = prof_mesh

bpy.ops.object.mode_set(mode = 'EDIT')

This code inspects the edges/verts, strings them in the correct order, makes a new mesh, replaces the current object.data (mesh) with it.


#terminal output.
==================================================
[0, Vector((1.0, 0.9999999403953552, 0.0))]
[1, Vector((0.9999999403953552, -0.9999999403953552, 0.0))]
[2, Vector((-1.0000001192092896, -0.9999998211860657, 0.0))]
[3, Vector((-0.9999996423721313, 1.0000003576278687, 0.0))]
[4, Vector((1.0, 0.0, 0.0))]
=== +
[0, [1, 2]]
[1, [2, 3]]
[2, [0, 4]]
[3, [1, 4]]
becomes
[0, [0, 1]]
[1, [1, 2]]
[2, [2, 3]]
[3, [3, 4]]
iteration: 0 1
end point 0
[0, 4, 1, 2, 3]
(1.0, 0.9999999403953552, 1.0)
(1.0, 0.0, 1.0)
(0.9999999403953552, -0.9999999403953552, 1.0)
(-1.0000001192092896, -0.9999998211860657, 1.0)
(-0.9999996423721313, 1.0000003576278687, 1.0)
[(0, 1), (1, 2), (2, 3), (3, 4)]
looks like

Deleting a face from a mesh

if you are modifying data of an object, sometimes you want to remove a face
# must be in edit mode.

# first select the face, then delete it.
mplane.data.faces[-1].select = True # won't show in viewport
bpy.ops.mesh.delete(type='ONLY_FACE') # will be acted on.
must be in edit mode for these options, other options for types are found by reading the tooltips in the delete menu.

July 01, 2011

adding faces to an existing mesh

update: use this, it uses BMesh:

this is old, deprecated code. with BMesh you don't have to enter object mode to update. be in objectmode before running this, or modify the code to do that for you.
import bpy

# start with adding a plane at 0,0,0  ,set 3d cursor to 0,0,0
bpy.ops.mesh.primitive_plane_add()
mplane = bpy.context.active_object

# in object mode 
if mplane.mode == 'EDIT':
    bpy.ops.object.mode_set(mode='OBJECT')

# add 4 verts
mplane.data.vertices.add(4)
mplane.data.vertices[-4].co = (1, -1, 1)
mplane.data.vertices[-3].co = (-1, -1, 1)
mplane.data.vertices[-2].co = (-1, 1, 1)
mplane.data.vertices[-1].co = (1, 1, 1)

for m in mplane.data.vertices:
    print(m.co)

print("mplane.data.faces - before")
for l in mplane.data.faces:
    print("face===")
    for em in l.vertices:
        print(em)
        
# add one face    
mplane.data.faces.add(1)
mplane.data.faces[-1].vertices_raw = [7,4,5,6]
mplane.data.update(calc_edges=True)

print("mplane.data.faces - after")
for l in mplane.data.faces:
    print("face===")
    for em in l.vertices:
        print(em)

print("num faces=", len(mplane.data.vertices))
print(mplane.data.faces[-1])

Sorting Edge keys Part I

this post has a second part to it here

This code is dying for a rewrite. The code takes a jumbled up sequence of edges that would normally describe a polyline or 'edgeloop', where the vertex sequence is comparable to edge_key_list below. This code assumes a few things.

1) no interuptions
2) no duplicates
3) you have already figured out the index of one of the end vertices.
4) the edge loop is not closed.

edge_key_list = [[5, 2],[2, 3],[3, 10],[10, 24],[24, 19],
[19, 6],[6, 1],[1, 20],[20, 13],[13, 9],[9, 12],[12, 8],[8, 23],[23, 15],
[15, 18],[18, 17],[17, 14],[11, 22],[22, 4],[4, 21],[14, 11],[0, 5],[0, 16],
[16, 7]]

def find_vert_connected(vert, mlist):
    if len(mlist) == 1:
       for g in mlist:
            for k in g:
                if k is not vert:
                    return(k, -1)
        
    for i in mlist:
        if vert in i:
            idx = mlist.index(i)
            for m in i:
                if m is not vert:
                    return(m, idx)

def generate_ladder(starter, edge_key_list):
    stairs = []
    while(True):
        stairs.append(starter)
        starter, idx = find_vert_connected(starter,  edge_key_list)
        if idx == -1:
            stairs.append(starter)
            break
        edge_key_list.pop(idx)

    return(stairs)

print(generate_ladder(7, edge_key_list))
#
# output will be this
'''
[7, 16, 0, 5, 2, 3, 10, 24, 19, 6, 1, 20, 13, 9, 12, 8, 23, 15, 18, 17, 14, 11, 22, 4, 21]
'''
inside blender this might look like

#mesh_edge_sequence_rectifier.py
'''
Sometimes when doing a lathe like operation such as with the Screw Modifier,
the order in which the verts are stringed together to create the profile
(polyline, edgeloop) becomes important to the calculation of face normals for
the screwed geometry. 

''' 
# assumption 1  : edge loop is not closed.
# assumptino 2  : no duplicates ,make sure no verts are identical?
# assumption 3  : includes non consequtive edge keys, ie [0,1],[3,2],..[2,1]
# assertion 1   : edge has a definite start/end, can be either of the two end points.

import bpy

edge_key_list = []
for i in bpy.context.active_object.data.edges:
    edge_key_list.append([i.vertices[0], i.vertices[1]])

'''
old
edge_key_list = [[5, 2],[2, 3],[3, 10],[10, 24],[24, 19],
[19, 6],[6, 1],[1, 20],[20, 13],[13, 9],[9, 12],[12, 8],[8, 23],[23, 15],
[15, 18],[18, 17],[17, 14],[11, 22],[22, 4],[4, 21],[14, 11],[0, 5],[0, 16],
[16, 7]]

'''
# count vertices. ( remember to add 1, because we start counting from 0 )
vert_count = len(edge_key_list)

'''
new 
new_key_list = [0,1],[1,2],[2,3],....[23,24]
'''
# new key list should look like the new one
new_key_list = []
for k in range(vert_count):
    new_key_list.append([k, k+1])

# find all verts that only occur once in the edge key list.
sort_list = []
for i in edge_key_list:
    for l in i:
        sort_list.append(l)


# could use this to figure out if there are interuptions in the edge loop
# here i will only prototype using count == 1.
start_vert = 0
for i in range(len(set(sort_list))):
    if sort_list.count(i) == 1:
        #print("use :", i)
        start_vert = i
        break
    

# performing a copy of the list
shrink_list = []
for i in edge_key_list:
    shrink_list.append(i)


# something about the looks of this function suggests it could be condensed to 4 lines
def find_vert_connected(vert, mlist):
    if len(mlist) == 1:
       for g in mlist:
            for k in g:
                if k is not vert:
                    return(k, -1)
        
    for i in mlist:
        if vert in i:
            idx = mlist.index(i)
            for m in i:
                if m is not vert:
                    return(m, idx)


def generate_ladder(starter, edge_key_list):
    stairs = []
    while(True):
        stairs.append(starter)
        starter, idx = find_vert_connected(starter,  edge_key_list)
        if idx == -1:
            stairs.append(starter)
            break
        edge_key_list.pop(idx)

    return(stairs)

numerically_sorted_list = generate_ladder(7, shrink_list)
print(numerically_sorted_list)
To find out which operations are the most costly on lists, check the python wiki: http://wiki.python.org/moin/TimeComplexity